Skip to content

Commit 4d73af5

Browse files
committed
feat(ai): refactor adapter and add growing schema
1 parent 58399e3 commit 4d73af5

File tree

4 files changed

+109
-63
lines changed

4 files changed

+109
-63
lines changed
Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import { ApolloLink } from "@apollo/client";
2-
import { print } from "graphql";
3-
import { BASE_SYSTEM_PROMPT } from "./consts.js";
42

53
export declare namespace AIAdapter {
64
export interface Options {
@@ -11,56 +9,14 @@ export declare namespace AIAdapter {
119
}
1210

1311
export abstract class AIAdapter {
14-
public providedSystemPrompt: string | undefined;
15-
protected systemPrompt: string;
12+
public systemPrompt?: string;
1613

17-
constructor(options: AIAdapter.Options = {}) {
18-
this.systemPrompt = BASE_SYSTEM_PROMPT;
19-
if (options.systemPrompt) {
20-
this.providedSystemPrompt = options.systemPrompt;
21-
this.systemPrompt += `\n\n${this.providedSystemPrompt}`;
22-
}
14+
constructor(options?: AIAdapter.Options) {
15+
this.systemPrompt = options?.systemPrompt;
2316
}
2417

25-
public generateResponseForOperation(
26-
operation: ApolloLink.Operation,
27-
prompt: string
28-
): Promise<AIAdapter.Result> {
29-
return Promise.resolve({ data: null });
30-
}
31-
32-
protected createPrompt(
33-
{ query, variables }: ApolloLink.Operation,
34-
prompt: string
35-
): string {
36-
// Try to get the GraphQL query document string
37-
// from the AST location if available, otherwise
38-
// use the `print` function to get the query string.
39-
//
40-
// The AST location may not be available if the query
41-
// was parsed with the `noLocation: true` option.
42-
//
43-
// If the query document string is available through
44-
// the AST location, it will save some processing time
45-
// over the `print` function.
46-
const queryString = query?.loc?.source?.body ?? print(query);
47-
48-
let promptVariables = "";
49-
if (variables) {
50-
promptVariables = `
51-
52-
With variables:
53-
\`\`\`json
54-
${JSON.stringify(variables, null, 2)}
55-
\`\`\``;
56-
}
57-
58-
return `Give me mock data that fulfills this query:
59-
\`\`\`graphql
60-
${queryString}
61-
\`\`\`
62-
${promptVariables}
63-
${prompt ? `\nAdditional instructions:\n${prompt}` : ""}
64-
`;
65-
}
18+
public abstract generateObject(
19+
prompt: string,
20+
systemPrompt: string
21+
): Promise<AIAdapter.Result>;
6622
}

packages/ai/src/mocking/AIMockLink.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ApolloLink, Observable } from "@apollo/client";
22
import { AIAdapter } from "./AIAdapter.js";
3+
import { BaseAIAdapter } from "./BaseAIAdapter.js";
34

45
export declare namespace AIMockLink {
56
export type DefaultOptions = {};
@@ -12,32 +13,28 @@ export declare namespace AIMockLink {
1213
}
1314

1415
export class AIMockLink extends ApolloLink {
15-
private adapter: AIAdapter;
16+
private adapter: BaseAIAdapter;
1617
public showWarnings: boolean = true;
1718

1819
public static defaultOptions: AIMockLink.DefaultOptions = {};
1920

2021
constructor(options: AIMockLink.Options) {
2122
super();
2223

23-
this.adapter = options.adapter;
24+
this.adapter = new BaseAIAdapter(options.adapter);
2425
this.showWarnings = options.showWarnings ?? true;
2526
}
2627

2728
public request(
2829
operation: ApolloLink.Operation
2930
): Observable<ApolloLink.Result> {
30-
const prompt = operation.getContext().prompt;
31-
3231
return new Observable((observer) => {
3332
try {
34-
this.adapter
35-
.generateResponseForOperation(operation, prompt)
36-
.then((result) => {
37-
// Notify the observer with the generated response
38-
observer.next(result);
39-
observer.complete();
40-
});
33+
this.adapter.performQuery(operation).then((result) => {
34+
// Notify the observer with the generated response
35+
observer.next(result);
36+
observer.complete();
37+
});
4138
} catch (error) {
4239
observer.error(error);
4340
observer.complete();
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { ApolloLink } from "@apollo/client";
2+
import { AIAdapter } from "./AIAdapter.js";
3+
import { print } from "graphql";
4+
import { BASE_SYSTEM_PROMPT } from "./consts.js";
5+
import { GrowingSchema } from "./GrowingSchema.js";
6+
7+
export class BaseAIAdapter {
8+
private static baseSystemPrompt = BASE_SYSTEM_PROMPT;
9+
private schema: GrowingSchema;
10+
11+
constructor(private implementation: AIAdapter) {
12+
this.schema = new GrowingSchema();
13+
}
14+
15+
/**
16+
* Performs a query using the implementation adapter.
17+
* @param operation - The operation to perform.
18+
* @returns The result of the query.
19+
*/
20+
public async performQuery(
21+
operation: ApolloLink.Operation
22+
): Promise<AIAdapter.Result> {
23+
const systemPrompt = BaseAIAdapter.createSystemPrompt(
24+
this.implementation.systemPrompt
25+
);
26+
27+
const prompt = BaseAIAdapter.createPrompt(operation);
28+
29+
const result = await this.implementation.generateObject(
30+
prompt,
31+
systemPrompt
32+
);
33+
34+
this.schema.add(operation, result);
35+
36+
return result;
37+
}
38+
39+
/**
40+
* Creates a system prompt from the base system prompt and the provided prompt.
41+
* @param prompt - The prompt to add to the base system prompt.
42+
* @returns The system prompt.
43+
*/
44+
private static createSystemPrompt(prompt?: string) {
45+
return [BaseAIAdapter.baseSystemPrompt, prompt]
46+
.filter(Boolean)
47+
.join("\n\n");
48+
}
49+
50+
/**
51+
* Creates a prompt from the operation.
52+
* @param operation - The operation to create a prompt from.
53+
* @returns The prompt.
54+
*/
55+
private static createPrompt(operation: ApolloLink.Operation): string {
56+
const { query, variables } = operation;
57+
const providedPrompt = operation.getContext().prompt;
58+
59+
// Try to get the GraphQL query document string
60+
// from the AST location if available, otherwise
61+
// use the `print` function to get the query string.
62+
//
63+
// The AST location may not be available if the query
64+
// was parsed with the `noLocation: true` option.
65+
//
66+
// If the query document string is available through
67+
// the AST location, it will save some processing time
68+
// over the `print` function.
69+
const queryString = query?.loc?.source?.body ?? print(query);
70+
71+
const promptParts = [
72+
"Give me mock data that fulfills this query:",
73+
"```graphql",
74+
queryString,
75+
"```",
76+
];
77+
78+
if (variables) {
79+
promptParts.push(
80+
"\n",
81+
"```json",
82+
JSON.stringify(variables, null, 2),
83+
"```"
84+
);
85+
}
86+
87+
if (providedPrompt) {
88+
promptParts.push("\n", "Additional instructions:", providedPrompt);
89+
}
90+
91+
return promptParts.join("\n");
92+
}
93+
}

packages/ai/src/mocking/__tests__/AIAdapter.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AIAdapter } from "../AIAdapter.js";
22

33
class DerivedAdapter extends AIAdapter {
44
constructor() {
5-
super();
5+
super({});
66
}
77

88
public generateObject(prompt: string): Promise<any> {

0 commit comments

Comments
 (0)