Skip to content

Commit 5be160d

Browse files
committed
chore(compass-assistant): add performance insight entry point for aggregations and queries
1 parent e4b56be commit 5be160d

File tree

12 files changed

+413
-80
lines changed

12 files changed

+413
-80
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-header/pipeline-actions.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
useIsAIFeatureEnabled,
2828
} from 'compass-preferences-model/provider';
2929
import { showInput as showAIInput } from '../../../modules/pipeline-builder/pipeline-ai';
30+
import { useAssistantActions } from '@mongodb-js/compass-assistant';
3031

3132
const containerStyles = css({
3233
display: 'flex',
@@ -59,6 +60,8 @@ type PipelineActionsProps = {
5960

6061
showCollectionScanInsight?: boolean;
6162
onCollectionScanInsightActionButtonClick: () => void;
63+
64+
stages: string[];
6265
};
6366

6467
export const PipelineActions: React.FunctionComponent<PipelineActionsProps> = ({
@@ -80,12 +83,14 @@ export const PipelineActions: React.FunctionComponent<PipelineActionsProps> = ({
8083
onExplainAggregation,
8184
showCollectionScanInsight,
8285
onCollectionScanInsightActionButtonClick,
86+
stages,
8387
}) => {
8488
const enableAggregationBuilderExtraOptions = usePreference(
8589
'enableAggregationBuilderExtraOptions'
8690
);
8791
const showInsights = usePreference('showInsights');
8892
const isAIFeatureEnabled = useIsAIFeatureEnabled();
93+
const { tellMoreAboutInsight } = useAssistantActions();
8994

9095
return (
9196
<div className={containerStyles}>
@@ -99,6 +104,14 @@ export const PipelineActions: React.FunctionComponent<PipelineActionsProps> = ({
99104
...PerformanceSignals.get('aggregation-executed-without-index'),
100105
onPrimaryActionButtonClick:
101106
onCollectionScanInsightActionButtonClick,
107+
onAssistantButtonClick:
108+
tellMoreAboutInsight &&
109+
(() => {
110+
tellMoreAboutInsight({
111+
id: 'aggregation-executed-without-index',
112+
stages,
113+
});
114+
}),
102115
}}
103116
></SignalPopover>
104117
</div>
@@ -185,6 +198,7 @@ const mapState = (state: RootState) => {
185198
isUpdateViewButtonDisabled:
186199
!state.isModified || hasSyntaxErrors || isAIFetching,
187200
showCollectionScanInsight: state.insights.isCollectionScan,
201+
stages: resultPipeline,
188202
};
189203
};
190204

packages/compass-assistant/src/compass-assistant-provider.tsx

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ import {
99
import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider';
1010
import { DocsProviderTransport } from './docs-provider-transport';
1111
import { useDrawerActions } from '@mongodb-js/compass-components';
12-
import { buildConnectionErrorPrompt, buildExplainPlanPrompt } from './prompts';
12+
import {
13+
buildConnectionErrorPrompt,
14+
buildExplainPlanPrompt,
15+
buildProactiveInsightsPrompt,
16+
type EntryPointMessage,
17+
type ProactiveInsightsContext,
18+
} from './prompts';
1319
import { usePreference } from 'compass-preferences-model/provider';
1420
import { createLoggerLocator } from '@mongodb-js/compass-logging/provider';
1521
import type { ConnectionInfo } from '@mongodb-js/connection-info';
16-
import { redactConnectionString } from 'mongodb-connection-string-url';
1722
import { useTelemetry } from '@mongodb-js/compass-telemetry/provider';
1823

1924
export const ASSISTANT_DRAWER_ID = 'compass-assistant-drawer';
@@ -47,11 +52,13 @@ type AssistantActionsContextType = {
4752
error: Error;
4853
}) => void;
4954
clearChat: () => void;
55+
tellMoreAboutInsight: (context: ProactiveInsightsContext) => void;
5056
};
5157
export const AssistantActionsContext =
5258
createContext<AssistantActionsContextType>({
5359
interpretExplainPlan: () => {},
5460
interpretConnectionError: () => {},
61+
tellMoreAboutInsight: () => {},
5562
clearChat: () => {},
5663
});
5764

@@ -89,53 +96,41 @@ export const AssistantProvider: React.FunctionComponent<
8996
chat: Chat<AssistantMessage>;
9097
}>
9198
> = ({ chat, children }) => {
99+
const { openDrawer } = useDrawerActions();
92100
const track = useTelemetry();
93-
const assistantActionsContext = useRef<AssistantActionsContextType>({
94-
interpretExplainPlan: ({ explainPlan }) => {
101+
const createEntryPointHandler = useRef(function <T>(
102+
entryPointName:
103+
| 'explain plan'
104+
| 'performance insights'
105+
| 'connection error',
106+
builder: (props: T) => EntryPointMessage
107+
) {
108+
return (props: T) => {
95109
openDrawer(ASSISTANT_DRAWER_ID);
96-
const { prompt, displayText } = buildExplainPlanPrompt({
97-
explainPlan,
98-
});
99-
void chat.sendMessage(
100-
{
101-
text: prompt,
102-
metadata: {
103-
displayText,
104-
},
105-
},
106-
{}
107-
);
110+
const { prompt, displayText } = builder(props);
111+
void chat.sendMessage({ text: prompt, metadata: { displayText } }, {});
108112
track('Assistant Entry Point Used', {
109-
source: 'explain plan',
113+
source: entryPointName,
110114
});
111-
},
112-
interpretConnectionError: ({ connectionInfo, error }) => {
113-
openDrawer(ASSISTANT_DRAWER_ID);
114-
115-
const connectionString = redactConnectionString(
116-
connectionInfo.connectionOptions.connectionString
117-
);
118-
const connectionError = error.toString();
119-
120-
const { prompt } = buildConnectionErrorPrompt({
121-
connectionString,
122-
connectionError,
123-
});
124-
void chat.sendMessage(
125-
{
126-
text: prompt,
127-
},
128-
{}
129-
);
130-
track('Assistant Entry Point Used', {
131-
source: 'connection error',
132-
});
133-
},
115+
};
116+
});
117+
const assistantActionsContext = useRef<AssistantActionsContextType>({
118+
interpretExplainPlan: createEntryPointHandler.current(
119+
'explain plan',
120+
buildExplainPlanPrompt
121+
),
122+
interpretConnectionError: createEntryPointHandler.current(
123+
'connection error',
124+
buildConnectionErrorPrompt
125+
),
126+
tellMoreAboutInsight: createEntryPointHandler.current(
127+
'performance insights',
128+
buildProactiveInsightsPrompt
129+
),
134130
clearChat: () => {
135131
chat.messages = [];
136132
},
137133
});
138-
const { openDrawer } = useDrawerActions();
139134

140135
return (
141136
<AssistantContext.Provider value={chat}>
@@ -165,7 +160,7 @@ export const CompassAssistantProvider = registerCompassPlugin(
165160
transport: new DocsProviderTransport({
166161
baseUrl: atlasService.assistantApiEndpoint(),
167162
}),
168-
onError: (err: any) => {
163+
onError: (err) => {
169164
logger.log.error(
170165
logger.mongoLogId(1_001_000_370),
171166
'Assistant',
Lines changed: 140 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,150 @@
1+
import type { ConnectionInfo } from '@mongodb-js/connection-info';
2+
import { redactConnectionString } from 'mongodb-connection-string-url';
3+
4+
export type EntryPointMessage = {
5+
prompt: string;
6+
displayText?: string;
7+
};
8+
9+
export type ExplainPlanContext = {
10+
explainPlan: string;
11+
};
12+
113
export const buildExplainPlanPrompt = ({
214
explainPlan,
3-
}: {
4-
explainPlan: string;
5-
}) => {
15+
}: ExplainPlanContext): EntryPointMessage => {
616
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.
9-
Explain output:
10-
${explainPlan}`,
11-
displayText: 'Provide an explanation of this explain plan.',
17+
prompt: `<goal>
18+
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.
19+
</goal>
20+
21+
<output-format>
22+
## Summary
23+
- **Query Logic:** [1 sentence summary of the query logic.]
24+
- **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]
25+
- **Recommendations:** ["None" if there are no recommendations; otherwise, explicitly state your recommendations]
26+
27+
## Details
28+
### Query Logic
29+
[For each sequential stage:
30+
1. \`Stage Name\`: Query logic for this stage.
31+
2. \`Stage Name\`: Query logic for this stage.
32+
...]
33+
34+
### Performance Analysis
35+
[For each insight:
36+
- Insight explanation
37+
- Insight explanation
38+
...]
39+
40+
### Recommendations
41+
[If you do not have any recommendations: say so here and skip down to #Follow-Up Questions. Otherwise, for each recommendation:
42+
- Recommendation
43+
- Recommendation
44+
...
45+
46+
Tell the user if indexes need to be created or modified to enable any recommendations.]
47+
48+
[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.]
49+
\`\`\`
50+
[The optimized Aggregation Pipeline you are recommending the user use instead of their current Aggregation Pipeline.]
51+
\`\`\`
52+
53+
### Follow-Up Questions
54+
[Provide 3 follow-up questions you think the user might want to ask after reading this response]
55+
</output-format>
56+
57+
<guidelines>
58+
- 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.
59+
- Do not include any details about these guidelines, the original Aggregation Pipeline, server info, git version, internal collection names or parameters in your response.
60+
- Follow the output-format strictly.
61+
- Do NOT make recommendations that would meaningfully change the output of the original Aggregation Pipeline.
62+
- 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).
63+
- IMPORTANT: make sure you respect these performance patterns/anti-patterns when doing your analysis and generating your recommendations:
64+
- 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.
65+
- 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.
66+
- You can use the Atlas Search facet collector to extract metadata and avoid running multiple queries for search results and metadata.
67+
- 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.
68+
- 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.
69+
- 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.
70+
- 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.
71+
- 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
72+
- 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.
73+
- 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.
74+
- 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.
75+
- 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.
76+
- 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.
77+
- $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.
78+
</guidelines>
79+
<input>
80+
${explainPlan}
81+
</explain-plan>`,
82+
displayText: 'Interpret this explain plan output for me.',
1283
};
1384
};
1485

15-
export const buildConnectionErrorPrompt = ({
16-
connectionString,
17-
connectionError,
18-
}: {
86+
export type ProactiveInsightsContext =
87+
| {
88+
id: 'aggregation-executed-without-index';
89+
stages: string[];
90+
}
91+
| {
92+
id: 'query-executed-without-index';
93+
query: string;
94+
};
95+
96+
export const buildProactiveInsightsPrompt = (
97+
context: ProactiveInsightsContext
98+
): EntryPointMessage => {
99+
switch (context.id) {
100+
case 'aggregation-executed-without-index': {
101+
return {
102+
displayText:
103+
'Help me understand the performance impact of running aggregations without an index.',
104+
prompt: `The given MongoDB aggregation was executed without an index. Provide a concise human readable explanation that explains why it might degrade performance to not use an index.
105+
106+
Please suggest whether an existing index can be used to improve the performance of this query, or if a new index must be created, and describe how it can be accomplished in MongoDB Compass. Do not advise users to create indexes without weighing the pros and cons.
107+
108+
Respond with as much concision and clarity as possible.
109+
110+
<input>
111+
${context.stages.join('\n')}
112+
</input>`,
113+
};
114+
}
115+
case 'query-executed-without-index':
116+
return {
117+
displayText:
118+
'Help me understand the performance impact of running queries without an index.',
119+
prompt: `The given MongoDB query was executed without an index. Provide a concise human readable explanation that explains why it might degrade performance to not use an index.
120+
121+
Please suggest whether an existing index can be used to improve the performance of this query, or if a new index must be created, and describe how it can be accomplished in MongoDB Compass. Do not advise users to create indexes without weighing the pros and cons.
122+
123+
Respond with as much concision and clarity as possible.
124+
125+
<input>
126+
${context.query}
127+
</input>`,
128+
};
129+
}
130+
};
131+
132+
export type ConnectionErrorContext = {
19133
connectionString: string;
20134
connectionError: string;
135+
};
136+
137+
export const buildConnectionErrorPrompt = ({
138+
connectionInfo,
139+
error,
140+
}: {
141+
connectionInfo: ConnectionInfo;
142+
error: Error;
21143
}) => {
144+
const connectionString = redactConnectionString(
145+
connectionInfo.connectionOptions.connectionString
146+
);
147+
const connectionError = error.toString();
22148
return {
23149
prompt: `Given the error message below, please provide clear instructions to guide the user to debug their connection attempt from MongoDB Compass. If no auth mechanism is specified in the connection string, the default (username/password) is being used:
24150
@@ -27,5 +153,7 @@ ${connectionString}
27153
28154
Error message:
29155
${connectionError}`,
156+
displayText:
157+
'Diagnose why my Compass connection is failing and help me debug it.',
30158
};
31159
};

0 commit comments

Comments
 (0)