Skip to content

Commit ef317d6

Browse files
committed
chore: WIP, update to use the new providers
1 parent 41c77a5 commit ef317d6

File tree

11 files changed

+5229
-10630
lines changed

11 files changed

+5229
-10630
lines changed

snippets/ai/package-lock.json

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

snippets/ai/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gagik.co/snippet-ai",
3-
"snippetName": "ai",
3+
"snippetName": "experimental-ai",
44
"version": "0.0.11",
55
"description": "Provides a ai command suite for mongosh to ask for MongoDB query expressions in natural language.",
66
"author": "Gagik Amaryan",
@@ -16,13 +16,14 @@
1616
"prepare": "npm run build"
1717
},
1818
"dependencies": {
19-
"@ai-sdk/mistral": "^1.2.8",
20-
"@ai-sdk/openai": "^1.3.22",
19+
"@ai-sdk/mistral": "^2.0.20",
20+
"@ai-sdk/openai": "^2.0.55",
21+
"@ai-sdk/openai-compatible": "^1.0.23",
2122
"@mongodb-js/devtools-connect": "^3.7.2",
2223
"@mongodb-js/oidc-plugin": "^1.1.7",
23-
"ai": "^4.3.15",
24+
"ai": "^5.0.81",
2425
"chalk": "^4.1.2",
25-
"ollama-ai-provider": "^1.2.0",
26+
"ollama-ai-provider-v2": "^1.5.1",
2627
"open": "^10.1.2"
2728
},
2829
"devDependencies": {
@@ -31,7 +32,6 @@
3132
"@mongodb-js/eslint-config-devtools": "^0.9.11",
3233
"@mongodb-js/prettier-config-devtools": "^1.0.2",
3334
"@types/node": "^20.17.48",
34-
"mongodb-rag-core": "^0.7.0",
3535
"mongodb-schema": "^12.6.2",
3636
"typescript": "^5.0.0"
3737
}

snippets/ai/src/ai.ts

Lines changed: 135 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,153 @@
1-
import { EmptyAiProvider, type AiProvider } from './providers/ai-provider.js';
2-
import { Config, ConfigSchema } from './config.js';
3-
import { wrapAllFunctions, type CliContext } from './helpers.js';
4-
import { getAiSdkProvider, models, type Models } from './providers/generic/ai-sdk-provider.js';
5-
import { getDocsAiProvider } from './providers/docs/docs-ai-provider.js';
1+
import { AiProvider } from './providers/ai-provider.js';
2+
import { Config } from './config.js';
3+
import {
4+
formatHelpCommands,
5+
wrapAllFunctions,
6+
type CliContext,
7+
} from './helpers.js';
8+
import { models } from './providers/models.js';
69
import { aiCommand } from './decorators.js';
710
import chalk from 'chalk';
811

9-
module.exports = (async (globalThis: CliContext) => {
10-
class AI {
11-
private readonly replConfig: {
12-
set: (key: string, value: unknown) => Promise<void>;
13-
get: <T>(key: string) => Promise<T>;
14-
};
15-
16-
private ai: AiProvider;
17-
public config: Config;
18-
19-
constructor(private readonly cliContext: CliContext) {
20-
const instanceState = this.cliContext.db._mongo._instanceState;
21-
22-
this.replConfig = {
23-
set: (key, value) =>
24-
instanceState.evaluationListener.setConfig(`snippet_ai_${key}`, value),
25-
get: (key) =>
26-
instanceState.evaluationListener.getConfig(`snippet_ai_${key}`),
27-
};
28-
29-
this.config = new Config(this.replConfig);
30-
31-
// Set up provider change listener
32-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
33-
this.config.on('change', async (event) => {
34-
switch (event.key) {
35-
case 'provider':
36-
this.ai = this.getProvider(event.value as ConfigSchema['provider']);
37-
break;
38-
case 'model':
39-
if (!Object.keys(models).includes(this.config.get('provider') as Models)) {
40-
if (event.value === 'default') {
41-
return;
42-
}
43-
await this.config.set('model', 'default');
44-
throw new Error(`${this.config.get('provider')} does not support custom models`);
45-
}
46-
try {
47-
this.ai = getAiSdkProvider(
48-
models[this.config.get('provider') as keyof typeof models](
49-
event.value === 'default' ? undefined : event.value as string,
50-
),
51-
this.cliContext,
52-
this.config,
53-
);
54-
} catch (error) {
55-
throw new Error(`Invalid model, please ensure your name is correct: ${error as string}`);
56-
}
57-
break;
58-
default:
59-
break;
60-
}
61-
});
62-
63-
this.ai = this.getProvider(
64-
process.env.MONGOSH_AI_PROVIDER as ConfigSchema['provider'] | undefined,
65-
);
66-
wrapAllFunctions(this.cliContext, this);
12+
module.exports = async (globalThis: CliContext) => {
13+
class AI {
14+
private readonly ai: AiProvider;
15+
public config: Config;
6716

68-
}
69-
70-
async setup() {
71-
await this.config.setup();
72-
73-
this.ai = this.getProvider(this.config.get('provider'));
74-
}
75-
76-
private getProvider(
77-
provider: ConfigSchema['provider'] | undefined,
78-
): AiProvider {
79-
switch (provider) {
80-
case 'docs':
81-
return getDocsAiProvider(this.cliContext, this.config);
82-
case 'openai':
83-
case 'mistral':
84-
case 'ollama': {
85-
const model = this.config.get('model');
86-
return getAiSdkProvider(
87-
models[provider](model === 'default' ? undefined : model),
88-
this.cliContext,
89-
this.config,
90-
);
91-
}
92-
default:
93-
return new EmptyAiProvider(this.cliContext, this.config);
17+
constructor({ ai, config }: { ai: AiProvider; config: Config }) {
18+
this.ai = ai;
19+
this.config = config;
9420
}
95-
}
96-
97-
@aiCommand()
98-
async shell(prompt: string) {
99-
await this.ai.shell(prompt);
100-
}
10121

102-
@aiCommand()
103-
async general(prompt: string) {
104-
await this.ai.general(prompt);
105-
}
22+
static async create(cliContext: CliContext): Promise<AI> {
23+
const instanceState = cliContext.db._mongo._instanceState;
24+
25+
const replConfig = {
26+
set: (key: string, value: unknown) =>
27+
instanceState.evaluationListener.setConfig(
28+
`snippet_ai_${key}`,
29+
value,
30+
),
31+
get: <T>(key: string): Promise<T> =>
32+
instanceState.evaluationListener.getConfig(`snippet_ai_${key}`),
33+
};
34+
35+
const config = await Config.create(replConfig);
36+
37+
const provider = new AiProvider(
38+
cliContext,
39+
config,
40+
models[config.get('provider') as keyof typeof models](
41+
config.get('model') === 'default' ? undefined : config.get('model'),
42+
),
43+
);
44+
45+
const ai = new AI({
46+
ai: provider,
47+
config,
48+
});
49+
50+
wrapAllFunctions(cliContext, ai);
51+
return ai;
52+
}
10653

107-
@aiCommand()
108-
async data(prompt: string) {
109-
await this.ai.data(prompt);
110-
}
54+
@aiCommand()
55+
async shell(prompt: string) {
56+
await this.ai.shell(prompt);
57+
}
11158

112-
@aiCommand()
113-
async query(prompt: string) {
114-
await this.ai.query(prompt);
115-
}
59+
@aiCommand()
60+
async query(prompt: string) {
61+
await this.ai.query(prompt);
62+
}
11663

117-
@aiCommand()
118-
async ask(prompt: string) {
119-
await this.ai.ask(prompt);
120-
}
64+
@aiCommand()
65+
async aggregate(prompt: string) {
66+
await this.ai.aggregate(prompt);
67+
}
12168

122-
@aiCommand()
123-
async aggregate(prompt: string) {
124-
await this.ai.aggregate(prompt);
125-
}
69+
@aiCommand()
70+
async ask(prompt: string) {
71+
await this.ai.processResponse(prompt, {
72+
systemPrompt:
73+
'You are a MongoDB and mongosh expert. Give brief answers without any formatting.',
74+
expectedOutput: 'response',
75+
});
76+
}
12677

127-
@aiCommand({requiresPrompt: false})
128-
help() {
129-
this.ai.help({
130-
provider: this.config.get('provider'),
131-
model: this.config.get('model'),
132-
});
133-
}
78+
@aiCommand({ requiresPrompt: false })
79+
help() {
80+
const commands = [
81+
{
82+
cmd: 'ai.ask',
83+
desc: 'ask MongoDB questions',
84+
example: 'ai.ask how do I run queries in mongosh?',
85+
},
86+
{
87+
cmd: 'ai.query',
88+
desc: 'generate a MongoDB query',
89+
example: 'ai.query find documents where name = "Ada"',
90+
},
91+
{
92+
cmd: 'ai.aggregate',
93+
desc: 'generate a MongoDB aggregation',
94+
example: 'ai.aggregate find documents where name = "Ada"',
95+
},
96+
{
97+
cmd: 'ai.collection',
98+
desc: 'set the active collection',
99+
example: 'ai.collection("users")',
100+
},
101+
{
102+
cmd: 'ai.shell',
103+
desc: 'generate administrative mongosh commands',
104+
example: 'ai.shell insert a new sample document',
105+
},
106+
{
107+
cmd: 'ai.config',
108+
desc: 'configure the AI commands',
109+
example: 'ai.config.set("provider", "ollama")',
110+
},
111+
];
112+
113+
this.ai.respond(
114+
formatHelpCommands(commands, {
115+
provider: this.config.get('provider'),
116+
model: this.config.get('model'),
117+
collection: this.ai.activeCollection,
118+
}),
119+
);
120+
}
134121

135-
@aiCommand()
136-
clear() {
137-
this.ai.clear();
138-
}
122+
@aiCommand()
123+
clear() {
124+
this.ai.clear();
125+
}
139126

140-
@aiCommand()
141-
collection(name: string) {
142-
this.ai.collection(name);
143-
}
127+
@aiCommand()
128+
collection(name: string) {
129+
this.ai.collection(name);
130+
}
144131

145-
@aiCommand()
146-
async provider(provider: string) {
147-
await this.config.set('provider', provider);
148-
this.ai.respond(`Switched to ${chalk.blue(provider)} provider`);
149-
}
132+
@aiCommand()
133+
async provider(provider: string) {
134+
await this.config.set('provider', provider);
135+
this.ai.respond(`Switched to ${chalk.blue(provider)} provider`);
136+
}
150137

151-
@aiCommand()
152-
async model(model: string) {
153-
await this.config.set('model', model);
154-
this.ai.respond(`Switched to ${chalk.blue(model)} model`);
155-
}
138+
@aiCommand()
139+
async model(model: string) {
140+
await this.config.set('model', model);
141+
this.ai.respond(`Switched to ${chalk.blue(model)} model`);
142+
}
156143

157-
[Symbol.for('nodejs.util.inspect.custom')]() {
158-
this.help();
159-
return '';
144+
[Symbol.for('nodejs.util.inspect.custom')]() {
145+
this.help();
146+
return '';
147+
}
160148
}
161-
}
162-
163-
164-
(globalThis as unknown as CliContext).ai = new AI(globalThis as unknown as CliContext);
165-
await (globalThis as unknown as {
166-
ai: AI;
167-
}).ai.setup();
168149

169-
});
150+
(globalThis as unknown as CliContext).ai = await AI.create(
151+
globalThis as unknown as CliContext,
152+
);
153+
};

snippets/ai/src/config.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class Config extends EventEmitter<{
3232
}> {
3333
private configMap: Record<ConfigKeys, ConfigSchema[ConfigKeys]> = defaults;
3434

35-
constructor(
35+
private constructor(
3636
private readonly replConfig: {
3737
set: (key: string, value: unknown) => Promise<void>;
3838
get: <T>(key: string) => Promise<T>;
@@ -41,11 +41,16 @@ export class Config extends EventEmitter<{
4141
super();
4242
}
4343

44-
async setup(): Promise<void> {
44+
static async create(replConfig: {
45+
set: (key: string, value: unknown) => Promise<void>;
46+
get: <T>(key: string) => Promise<T>;
47+
}): Promise<Config> {
48+
const config = new Config(replConfig);
4549
const keys = Object.keys(configSchema.shape) as Array<keyof ConfigSchema>;
4650
for (const key of keys) {
47-
this.configMap[key] ??= (await this.replConfig.get(key));
51+
config.configMap[key] ??= await replConfig.get(key);
4852
}
53+
return config;
4954
}
5055

5156
get<K extends keyof ConfigSchema>(key: K): ConfigSchema[K] {
@@ -75,10 +80,10 @@ export class Config extends EventEmitter<{
7580
[Symbol.for('nodejs.util.inspect.custom')]() {
7681
const lines = Object.entries(configSchema.shape).map(([key, schema]) => {
7782
let type: string | undefined = undefined;
78-
if (schema._def.typeName === 'ZodEnum') {
79-
type = `${schema._def.values.join(' | ')}`;
83+
if ('values' in schema.def) {
84+
type = `${(schema.def.values as string[]).join(' | ')}`;
8085
}
81-
const i = (value: unknown) => inspect(value, {colors: true});
86+
const i = (value: unknown) => inspect(value, { colors: true });
8287

8388
return ` ${i(key)}: ${chalk.white(i(this.configMap[key as ConfigKeys]))},${type ? chalk.gray(` // ${type}`) : ''}`;
8489
});

snippets/ai/src/index.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// tsc assigns to a global exports variable for TypeScript files which doesn't pair well
22
// with mongosh scripts so we have this wrapper to load the module and minimize rewriting.
3+
34
(() => {
4-
const localRequire = require('module').createRequire(__filename);
5-
// eslint-disable-next-line no-undef
6-
localRequire('./ai.js')(globalThis);
7-
})();
5+
const localRequire = require('module').createRequire(__filename);
6+
// eslint-disable-next-line no-undef
7+
localRequire('./ai.js')(globalThis);
8+
})();

0 commit comments

Comments
 (0)