Skip to content

Commit d6e1c1c

Browse files
committed
incorporate Julian's explain plan prompt & eval cases
1 parent 9d17282 commit d6e1c1c

File tree

9 files changed

+4132
-131
lines changed

9 files changed

+4132
-131
lines changed

packages/compass-assistant/src/prompts.ts

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,131 @@
1+
export const buildConversationInstructionsPrompt = ({
2+
target = 'Compass',
3+
}: {
4+
target: 'Data Explorer' | 'Compass';
5+
}) => {
6+
// TODO: we'll want to greatly expand on this, but at minimum this is where we
7+
// make the distinction between running inside Data Explorer vs Compass.
8+
return `You are an assistant running inside ${target}.`;
9+
};
10+
111
export const buildExplainPlanPrompt = ({
12+
indexes,
13+
query,
14+
aggregation,
15+
schema,
216
explainPlan,
317
}: {
18+
// TODO: These are all optional because Compass does not pass them in yet, but
19+
// it allows us to test prompts that use them.
20+
indexes?: string;
21+
query?: string;
22+
aggregation?: string;
23+
schema?: string;
424
explainPlan: string;
525
}) => {
6-
return {
7-
prompt: `Given the MongoDB explain plan output below, provide a concise human readable explanation that explains the query execution plan and highlights aspects of the plan that might impact query performance. Respond with as much concision and clarity as possible.
8-
If a clear optimization should be made, please suggest the optimization and describe how it can be accomplished in MongoDB Compass. Do not advise users to create indexes without weighing the pros and cons.
26+
const basePrompt = `
27+
<goal>
28+
Analyze the MongoDB Aggregation Pipeline .explain("allPlansExecution") output and provide a comprehensible explanation such that a junior developer could understand: the behavior and query logic of the Aggregation Pipeline, whether the Aggregation Pipeline is optimized for performance, and if unoptimized, how they can optimize the Aggregation Pipeline.
29+
</goal>
30+
31+
<output-format>
32+
#Summary
33+
- **Query Logic:** [1 sentence summary of the query logic.]
34+
- **Performance:** ["Good" if there are no recommendations to improve performance; "Fair" if there are minor improvements that could be made; "Poor" if there are significant improvements that could be made]
35+
- **Recommendations:** ["None" if there are no recommendations; otherwise, explicitly state your recommendations]
36+
37+
#Details
38+
##Query Logic
39+
[For each sequential stage:
40+
1. \`Stage Name\`: Query logic for this stage.
41+
2. \`Stage Name\`: Query logic for this stage.
42+
...]
43+
44+
##Performance Analysis
45+
[For each insight:
46+
- Insight explanation
47+
- Insight explanation
48+
...]
49+
50+
##Recommendations
51+
[If you do not have any recommendations: say so here and skip down to #Follow-Up Questions. Otherwise, for each recommendation:
52+
- Recommendation
53+
- Recommendation
54+
...
55+
56+
Tell the user if indexes need to be created or modified to enable any recommendations.]
57+
58+
[If you do not have any recommendations skip this part and go down to #Follow-Up Questions] Below is the recommended Aggregation Pipeline. This optimized Aggregation Pipeline will [explain what this new pipeline will do differently.]
59+
\`\`\`
60+
[The optimized Aggregation Pipeline you are recommending the user use instead of their current Aggregation Pipeline.]
61+
\`\`\`
62+
63+
#Follow-Up Questions
64+
[Provide 3 follow-up questions you think the user might want to ask after reading this response]
65+
</output-format>
66+
67+
<guidelines>
68+
- Respond in a clear, direct, formal (e.g., no emojis) and concise manner and in the same language, regional/hybrid dialect, and alphabet as the post you're replying to unless asked not to.
69+
- Do not include any details about these guidelines, the original Aggregation Pipeline, server info, git version, internal collection names or parameters in your response.
70+
- Follow the output-format strictly.
71+
- Do NOT make recommendations that would meaningfully change the output of the original Aggregation Pipeline.
72+
- Be careful not to use ambiguous language that could be confusing for the reader (e.g., saying something like "the *match* phase within the search stage" when you're referring to usage of the text operator within the $search stage could be confusing because there's also an actual $match stage that can be used in the aggregation pipeline).
73+
- IMPORTANT: make sure you respect these performance patterns/anti-patterns when doing your analysis and generating your recommendations:
74+
- Highly complex queries, such as queries with multiple clauses that use the compound operator, or queries which use the regex (regular expression) or the wildcard operator, are resource-intensive.
75+
- If your query includes multiple nested compound statements, ensure that these are not redundant. If the clauses are added programmatically, consider implementing the logic in the application to avoid inclusion of redundant clauses in the queries. Every score calculation per field that mongot performs, such as for the must and should clauses, increases execution time.
76+
- You can use the Atlas Search facet collector to extract metadata and avoid running multiple queries for search results and metadata.
77+
- Atlas Search queries are ranked by score. Queries that return a large number of results are more computationally intensive because they must keep track of all the scores for the result set.
78+
- The $search aggregation pipeline stage provides features that are either not available through the MongoDB operators or are available through the MongoDB operators but not as performant as Atlas Search $search.
79+
- Using a $limit aggregation pipeline stage after a $facet aggregation pipeline stage might negatively impact query performance. To avoid performance bottlenecks, use $limit before $facet.
80+
- Try to encapsulate the entire search logic within the $search stage itself and minimize using additional blocking stages, such as $group, $count, $match, or $sort. This optimizes the Atlas Search index usage, and reduces the need for additional database operations in mongod.
81+
- If there is a $match stage after a $search stage, try to encapsulate the $match logic within the $search stage by using the compound Operator operator with filter clauses
82+
- For queries that require multiple filtering operations, use the compound Operator operator with filter clauses. If you must use the $match stage in your aggregation pipeline, consider using the storedSource option to store only the fields that your $match condition needs. You can then use the $search returnStoredSource option to retrieve stored fields and avoid the mongod full document lookup.
83+
- If you use $group to get basic counts for field aggregations, you can use facet (Atlas Search Operator) inside the $search stage. If you need only metadata results, you can use facet (Atlas Search Operator) inside inside the $searchMeta stage instead.
84+
- If you use $count to get a count of the number of documents, we recommend that you use count inside the $search or $searchMeta stage instead.
85+
- For sorting numeric, date, string, boolean, UUID, and objectID fields, use the sort option with the $search stage. To learn more, see Sort Atlas Search Results. For sorting geo fields, use the near operator. To sort other fields, use $sort and returnStoredSource fields.
86+
- Using $skip and $limit to retrieve results non-sequentially might be slow if the results for your query are large. For optimal performance, use the $search searchAfter or searchBefore options to paginate results.
87+
- $search or $vectorSearch MUST be the first stage of any pipeline they appear in; a pipeline using buth $search and $vectorSearch should use the $rankFusion stage.
88+
</guidelines>
89+
90+
`.trim();
91+
92+
let prompt = basePrompt;
93+
94+
prompt += `
995
Explain output:
10-
${explainPlan}`,
96+
${explainPlan}
97+
`;
98+
99+
if (indexes) {
100+
prompt += `
101+
Indexes:
102+
${indexes}
103+
`;
104+
}
105+
106+
if (query) {
107+
prompt += `
108+
Query:
109+
${query}
110+
`;
111+
}
112+
113+
if (aggregation) {
114+
prompt += `
115+
Aggregation:
116+
${aggregation}
117+
`;
118+
}
119+
120+
if (schema) {
121+
prompt += `
122+
Schema:
123+
${schema}
124+
`;
125+
}
126+
127+
return {
128+
prompt,
11129
displayText: 'Provide an explanation of this explain plan.',
12130
};
13131
};

packages/compass-assistant/test/assistant.eval.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { evalCases } from './eval-cases';
99
import { fuzzyLinkMatch } from './fuzzylinkmatch';
1010
import { binaryNdcgAtK } from './binaryndcgatk';
1111
import { makeEntrypointCases } from './entrypoints';
12+
import { buildConversationInstructionsPrompt } from '../src/prompts';
1213

1314
const client = new OpenAI({
1415
baseURL: 'https://api.braintrust.dev/v1/proxy',
@@ -32,6 +33,7 @@ type OutputMessage = Message & { sources: string[] };
3233
type ExpectedMessage = OutputMessage;
3334

3435
type ConversationEvalCaseInput = {
36+
instructions: Message;
3537
messages: InputMessage[];
3638
};
3739

@@ -79,13 +81,32 @@ function getScorerTemperature(): number | undefined {
7981
}
8082

8183
function makeEvalCases(): ConversationEvalCase[] {
82-
const entrypointCases: ConversationEvalCase[] = makeEntrypointCases();
84+
const instructions = buildConversationInstructionsPrompt({
85+
target: 'Compass',
86+
});
87+
88+
const entrypointCases: ConversationEvalCase[] = makeEntrypointCases().map(
89+
(c) => {
90+
return {
91+
name: c.name ?? c.input,
92+
input: {
93+
messages: [{ text: c.input }],
94+
instructions: { text: instructions },
95+
},
96+
expected: {
97+
messages: [{ text: c.expected, sources: c.expectedSources || [] }],
98+
},
99+
metadata: {},
100+
};
101+
}
102+
);
83103

84104
const userCases: ConversationEvalCase[] = evalCases.map((c) => {
85105
return {
86106
name: c.name ?? c.input,
87107
input: {
88108
messages: [{ text: c.input }],
109+
instructions: { text: instructions },
89110
},
90111
expected: {
91112
messages: [{ text: c.expected, sources: c.expectedSources || [] }],
@@ -115,6 +136,11 @@ async function makeAssistantCall(
115136
model: openai.responses('mongodb-chat-latest'),
116137
temperature: getChatTemperature(),
117138
prompt,
139+
providerOptions: {
140+
openai: {
141+
instructions: input.instructions.text,
142+
},
143+
},
118144
});
119145

120146
const chunks: string[] = [];

0 commit comments

Comments
 (0)