-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(nx-dev): new page for ai docs (#18025)
- Loading branch information
Showing
27 changed files
with
1,001 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"extends": ["../../.eslintrc.json"], | ||
"ignorePatterns": ["!**/*"], | ||
"overrides": [ | ||
{ | ||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.ts", "*.tsx"], | ||
"rules": {} | ||
}, | ||
{ | ||
"files": ["*.js", "*.jsx"], | ||
"rules": {} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# nx-dev-data-access-ai | ||
|
||
This library was generated with [Nx](https://nx.dev). | ||
|
||
## Building | ||
|
||
Run `nx build nx-dev-data-access-ai` to build the library. | ||
|
||
## Running unit tests | ||
|
||
Run `nx test nx-dev-data-access-ai` to execute the unit tests via [Jest](https://jestjs.io). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* eslint-disable */ | ||
export default { | ||
displayName: 'nx-dev-data-access-ai', | ||
preset: '../../jest.preset.js', | ||
testEnvironment: 'node', | ||
transform: { | ||
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }], | ||
}, | ||
moduleFileExtensions: ['ts', 'js', 'html'], | ||
coverageDirectory: '../../coverage/nx-dev/data-access-ai', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"name": "@nx/nx-dev/data-access-ai", | ||
"version": "0.0.1", | ||
"type": "commonjs" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "nx-dev-data-access-ai", | ||
"$schema": "../../node_modules/nx/schemas/project-schema.json", | ||
"sourceRoot": "nx-dev/data-access-ai/src", | ||
"projectType": "library", | ||
"targets": { | ||
"build": { | ||
"executor": "@nx/js:tsc", | ||
"outputs": ["{options.outputPath}"], | ||
"options": { | ||
"outputPath": "dist/nx-dev/data-access-ai", | ||
"main": "nx-dev/data-access-ai/src/index.ts", | ||
"tsConfig": "nx-dev/data-access-ai/tsconfig.lib.json", | ||
"assets": ["nx-dev/data-access-ai/*.md"] | ||
} | ||
}, | ||
"lint": { | ||
"executor": "@nx/linter:eslint", | ||
"outputs": ["{options.outputFile}"], | ||
"options": { | ||
"lintFilePatterns": ["nx-dev/data-access-ai/**/*.ts"] | ||
} | ||
}, | ||
"test": { | ||
"executor": "@nx/jest:jest", | ||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"], | ||
"options": { | ||
"jestConfig": "nx-dev/data-access-ai/jest.config.ts", | ||
"passWithNoTests": true | ||
}, | ||
"configurations": { | ||
"ci": { | ||
"ci": true, | ||
"codeCoverage": true | ||
} | ||
} | ||
} | ||
}, | ||
"tags": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './lib/data-access-ai'; | ||
export * from './lib/utils'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
// based on: | ||
// https://github.com/supabase-community/nextjs-openai-doc-search/blob/main/pages/api/vector-search.ts | ||
|
||
import { createClient } from '@supabase/supabase-js'; | ||
import GPT3Tokenizer from 'gpt3-tokenizer'; | ||
import { | ||
Configuration, | ||
OpenAIApi, | ||
CreateModerationResponse, | ||
CreateEmbeddingResponse, | ||
ChatCompletionRequestMessageRoleEnum, | ||
CreateCompletionResponseUsage, | ||
} from 'openai'; | ||
import { getMessageFromResponse, sanitizeLinksInResponse } from './utils'; | ||
|
||
const openAiKey = process.env['NX_OPENAI_KEY']; | ||
const supabaseUrl = process.env['NX_NEXT_PUBLIC_SUPABASE_URL']; | ||
const supabaseServiceKey = process.env['NX_SUPABASE_SERVICE_ROLE_KEY']; | ||
const config = new Configuration({ | ||
apiKey: openAiKey, | ||
}); | ||
const openai = new OpenAIApi(config); | ||
|
||
export async function nxDevDataAccessAi( | ||
query: string | ||
): Promise<{ textResponse: string; usage?: CreateCompletionResponseUsage }> { | ||
try { | ||
if (!openAiKey) { | ||
throw new ApplicationError('Missing environment variable NX_OPENAI_KEY'); | ||
} | ||
|
||
if (!supabaseUrl) { | ||
throw new ApplicationError( | ||
'Missing environment variable NX_NEXT_PUBLIC_SUPABASE_URL' | ||
); | ||
} | ||
|
||
if (!supabaseServiceKey) { | ||
throw new ApplicationError( | ||
'Missing environment variable NX_SUPABASE_SERVICE_ROLE_KEY' | ||
); | ||
} | ||
|
||
if (!query) { | ||
throw new UserError('Missing query in request data'); | ||
} | ||
|
||
const supabaseClient = createClient(supabaseUrl, supabaseServiceKey); | ||
|
||
// Moderate the content to comply with OpenAI T&C | ||
const sanitizedQuery = query.trim(); | ||
const moderationResponse: CreateModerationResponse = await openai | ||
.createModeration({ input: sanitizedQuery }) | ||
.then((res) => res.data); | ||
|
||
const [results] = moderationResponse.results; | ||
|
||
if (results.flagged) { | ||
throw new UserError('Flagged content', { | ||
flagged: true, | ||
categories: results.categories, | ||
}); | ||
} | ||
|
||
// Create embedding from query | ||
const embeddingResponse = await openai.createEmbedding({ | ||
model: 'text-embedding-ada-002', | ||
input: sanitizedQuery, | ||
}); | ||
|
||
if (embeddingResponse.status !== 200) { | ||
throw new ApplicationError( | ||
'Failed to create embedding for question', | ||
embeddingResponse | ||
); | ||
} | ||
|
||
const { | ||
data: [{ embedding }], | ||
}: CreateEmbeddingResponse = embeddingResponse.data; | ||
|
||
const { error: matchError, data: pageSections } = await supabaseClient.rpc( | ||
'match_page_sections', | ||
{ | ||
embedding, | ||
match_threshold: 0.78, | ||
match_count: 10, | ||
min_content_length: 50, | ||
} | ||
); | ||
|
||
if (matchError) { | ||
throw new ApplicationError('Failed to match page sections', matchError); | ||
} | ||
|
||
const tokenizer = new GPT3Tokenizer({ type: 'gpt3' }); | ||
let tokenCount = 0; | ||
let contextText = ''; | ||
|
||
for (let i = 0; i < pageSections.length; i++) { | ||
const pageSection = pageSections[i]; | ||
const content = pageSection.content; | ||
const encoded = tokenizer.encode(content); | ||
tokenCount += encoded.text.length; | ||
|
||
if (tokenCount >= 1500) { | ||
break; | ||
} | ||
|
||
contextText += `${content.trim()}\n---\n`; | ||
} | ||
|
||
const prompt = ` | ||
${` | ||
You are a knowledgeable Nx representative. | ||
Your knowledge is based entirely on the official Nx documentation. | ||
You should answer queries using ONLY that information. | ||
Answer in markdown format. Always give an example, answer as thoroughly as you can, and | ||
always provide a link to relevant documentation | ||
on the https://nx.dev website. All the links you find or post | ||
that look like local or relative links, always prepend with "https://nx.dev". | ||
Your answer should be in the form of a Markdown article, much like the | ||
existing Nx documentation. Include a title, and subsections, if it makes sense. | ||
Mark the titles and the subsections with the appropriate markdown syntax. | ||
If you are unsure and the answer is not explicitly written in the Nx documentation, say | ||
"Sorry, I don't know how to help with that. | ||
You can visit the [Nx documentation](https://nx.dev/getting-started/intro) for more info." | ||
Remember, answer the question using ONLY the information provided in the Nx documentation. | ||
Answer as markdown (including related code snippets if available). | ||
` | ||
.replace(/\s+/g, ' ') | ||
.trim()} | ||
`; | ||
|
||
const chatGptMessages = [ | ||
{ | ||
role: ChatCompletionRequestMessageRoleEnum.System, | ||
content: prompt, | ||
}, | ||
{ | ||
role: ChatCompletionRequestMessageRoleEnum.Assistant, | ||
content: contextText, | ||
}, | ||
{ | ||
role: ChatCompletionRequestMessageRoleEnum.User, | ||
content: sanitizedQuery, | ||
}, | ||
]; | ||
|
||
const response = await openai.createChatCompletion({ | ||
model: 'gpt-3.5-turbo-16k', | ||
messages: chatGptMessages, | ||
temperature: 0, | ||
stream: false, | ||
}); | ||
|
||
if (response.status !== 200) { | ||
const error = response.data; | ||
throw new ApplicationError('Failed to generate completion', error); | ||
} | ||
|
||
const message = getMessageFromResponse(response.data); | ||
|
||
const responseWithoutBadLinks = await sanitizeLinksInResponse(message); | ||
|
||
return { | ||
textResponse: responseWithoutBadLinks, | ||
usage: response.data.usage, | ||
}; | ||
} catch (err: unknown) { | ||
if (err instanceof UserError) { | ||
console.error(err.message); | ||
} else if (err instanceof ApplicationError) { | ||
// Print out application errors with their additional data | ||
console.error(`${err.message}: ${JSON.stringify(err.data)}`); | ||
} else { | ||
// Print out unexpected errors as is to help with debugging | ||
console.error(err); | ||
} | ||
|
||
// TODO: include more response info in debug environments | ||
console.error(err); | ||
throw err; | ||
} | ||
} | ||
export class ApplicationError extends Error { | ||
constructor(message: string, public data: Record<string, any> = {}) { | ||
super(message); | ||
} | ||
} | ||
|
||
export class UserError extends ApplicationError {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { CreateChatCompletionResponse } from 'openai'; | ||
|
||
export function getMessageFromResponse( | ||
response: CreateChatCompletionResponse | ||
): string { | ||
/** | ||
* | ||
* This function here will or may be enhanced | ||
* once we add more functionality | ||
*/ | ||
return response.choices[0].message?.content ?? ''; | ||
} | ||
|
||
export async function sanitizeLinksInResponse( | ||
response: string | ||
): Promise<string> { | ||
const regex = /https:\/\/nx\.dev[^) \n]*[^).]/g; | ||
const urls = response.match(regex); | ||
|
||
if (urls) { | ||
for (const url of urls) { | ||
const linkIsWrong = await is404(url); | ||
if (linkIsWrong) { | ||
response = response.replace( | ||
url, | ||
'https://nx.dev/getting-started/intro' | ||
); | ||
} | ||
} | ||
} | ||
|
||
return response; | ||
} | ||
|
||
async function is404(url: string): Promise<boolean> { | ||
try { | ||
const response = await fetch(url.replace('https://nx.dev', '')); | ||
if (response.status === 404) { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} catch (error) { | ||
if ((error as any)?.response?.status === 404) { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"extends": "../../tsconfig.base.json", | ||
"compilerOptions": { | ||
"module": "commonjs", | ||
"forceConsistentCasingInFileNames": true, | ||
"strict": true, | ||
"noImplicitOverride": true, | ||
"noPropertyAccessFromIndexSignature": true, | ||
"noImplicitReturns": true, | ||
"noFallthroughCasesInSwitch": true, | ||
"target": "es2021", | ||
"lib": ["es2021", "DOM"] | ||
}, | ||
"files": [], | ||
"include": [], | ||
"references": [ | ||
{ | ||
"path": "./tsconfig.lib.json" | ||
}, | ||
{ | ||
"path": "./tsconfig.spec.json" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"extends": "./tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "../../dist/out-tsc", | ||
"declaration": true, | ||
"types": ["node"] | ||
}, | ||
"include": ["src/**/*.ts"], | ||
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"extends": "./tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "../../dist/out-tsc", | ||
"module": "commonjs", | ||
"types": ["jest", "node"] | ||
}, | ||
"include": [ | ||
"jest.config.ts", | ||
"src/**/*.test.ts", | ||
"src/**/*.spec.ts", | ||
"src/**/*.d.ts" | ||
] | ||
} |
Oops, something went wrong.
6ccbbbc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
nx-dev – ./
nx.dev
nx-dev-git-master-nrwl.vercel.app
nx-five.vercel.app
nx-dev-nrwl.vercel.app