feat: llama3

This commit is contained in:
Greg Richardson committed 2024-04-26 19:15:34 -06:00
1 parent 45e04cfa63
commit 0963d40d20
22 files changed
+797 -333

No files matched your search

+2
View File
@@ -3,3 +3,5 @@
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
OPENAI_API_KEY=your-openai-api-key
OPENAI_BASE_URL=your-openai-compatible-base-url
OPENAI_MODEL=your-language-model
@@ -1,17 +1,12 @@
import { stripIndent } from 'common-tags'
import { cookies } from 'next/headers'
import OpenAI from 'openai'
// import is weird, what's up with this?
import { createClient } from '@/lib/supabase/server'
import { createOpenAiClient } from 'ai-commands'
import { ContextLengthError } from '../../../../../packages/ai-commands/src/errors'
import { getMessages } from './getMessages'
// Create an OpenAI API client (that's edge friendly!)
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
// Set the runtime to edge for best performance
export const runtime = 'edge'
@@ -26,6 +21,12 @@ export type AiAssistantMessage = {
* it.
*/
export const getAssistantResponse = async (threadId: string, messageId: string) => {
const { openai, model, error: openAiError } = createOpenAiClient()
if (openAiError) {
throw openAiError
}
const supabase = createClient()
const { data: message, error } = await supabase
@@ -91,7 +92,7 @@ export const getAssistantResponse = async (threadId: string, messageId: string)
try {
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo-16k-0613',
model,
messages: initMessages,
max_tokens: 1024,
temperature: 0,
+2 -1
View File
@@ -16,6 +16,7 @@
"@supabase/ssr": "^0.1.0",
"@tanstack/react-query": "^5.13.4",
"ai": "^2.2.31",
"ai-commands": "*",
"common": "*",
"config": "*",
"dayjs": "^1.11.10",
@@ -42,4 +43,4 @@
"postcss": "^8.4.31",
"typescript": "^5.4.3"
}
}
}
+8 -2
View File
@@ -6,7 +6,6 @@ DEFAULT_PROJECT_NAME=Default Project
DASHBOARD_USERNAME=supabase
DASHBOARD_PASSWORD=this_password_is_insecure_and_should_be_updated
SUPABASE_URL=http://localhost:8000
SUPABASE_PUBLIC_URL=http://localhost:8000
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
@@ -24,4 +23,11 @@ NEXT_PUBLIC_HCAPTCHA_SITE_KEY=10000000-ffff-ffff-ffff-000000000001
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhndWloeHV6cWlid3hqbmlteGV2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NzUwOTQ4MzUsImV4cCI6MTk5MDY3MDgzNX0.0PMlOxtKL4O9GGZuAP_Xl4f-Tut1qOnW4bNEmAtoB8w
NEXT_PUBLIC_SUPABASE_URL=https://xguihxuzqibwxjnimxev.supabase.co
DOCKER_SOCKET_LOCATION=/var/run/docker.sock
OPENAI_API_KEY="your-openai-api-key"
OPENAI_BASE_URL="your-openai-compatible-base-url"
OPENAI_MODEL="your-language-model"
OPENAI_EMBEDDING_API_KEY="your-openai-api-key"
OPENAI_EMBEDDING_BASE_URL="your-openai-compatible-base-url"
OPENAI_EMBEDDING_MODEL="your-embedding-model"
DOCKER_SOCKET_LOCATION=/var/run/docker.sock
+8 -11
View File
@@ -1,18 +1,9 @@
import { ContextLengthError, EmptySqlError, debugSql } from 'ai-commands'
import { createOpenAiClient } from 'ai-commands/src/openai'
import apiWrapper from 'lib/api/apiWrapper'
import { NextApiRequest, NextApiResponse } from 'next'
import { OpenAI } from 'openai'
const openAiKey = process.env.OPENAI_KEY
const openai = new OpenAI({ apiKey: openAiKey })
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!openAiKey) {
return res.status(500).json({
error: 'No OPENAI_KEY set. Create this environment variable to use AI features.',
})
}
const { method } = req
switch (method) {
@@ -25,12 +16,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
export async function handlePost(req: NextApiRequest, res: NextApiResponse) {
const { openai, model, error } = createOpenAiClient()
if (error) {
return res.status(500).json({ error: error.message })
}
const {
body: { errorMessage, sql, entityDefinitions },
} = req
try {
const result = await debugSql(openai, errorMessage, sql, entityDefinitions)
const result = await debugSql(openai, model, errorMessage, sql, entityDefinitions)
return res.json(result)
} catch (error) {
if (error instanceof Error) {
+8 -11
View File
@@ -1,18 +1,9 @@
import { ContextLengthError, EmptySqlError, editSql } from 'ai-commands'
import { createOpenAiClient } from 'ai-commands/src/openai'
import apiWrapper from 'lib/api/apiWrapper'
import { NextApiRequest, NextApiResponse } from 'next'
import { OpenAI } from 'openai'
const openAiKey = process.env.OPENAI_KEY
const openai = new OpenAI({ apiKey: openAiKey })
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!openAiKey) {
return res.status(500).json({
error: 'No OPENAI_KEY set. Create this environment variable to use AI features.',
})
}
const { method } = req
switch (method) {
@@ -25,12 +16,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
export async function handlePost(req: NextApiRequest, res: NextApiResponse) {
const { openai, model, error } = createOpenAiClient()
if (error) {
return res.status(500).json({ error: error.message })
}
const {
body: { prompt, sql, entityDefinitions },
} = req
try {
const result = await editSql(openai, prompt, sql, entityDefinitions)
const result = await editSql(openai, model, prompt, sql, entityDefinitions)
return res.json(result)
} catch (error) {
if (error instanceof Error) {
+15 -17
View File
@@ -1,25 +1,11 @@
import { StreamingTextResponse } from 'ai'
import { generateV2 } from 'ai-commands/edge'
import { createOpenAiClient } from 'ai-commands/src/openai'
import { NextRequest } from 'next/server'
import OpenAI from 'openai'
export const runtime = 'edge'
const openAiKey = process.env.OPENAI_KEY
export default async function handler(req: NextRequest) {
if (!openAiKey) {
return new Response(
JSON.stringify({
error: 'No OPENAI_KEY set. Create this environment variable to use AI features.',
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
)
}
const { method } = req
switch (method) {
@@ -37,7 +23,19 @@ export default async function handler(req: NextRequest) {
}
async function handlePost(request: NextRequest) {
const openai = new OpenAI({ apiKey: openAiKey })
const { openai, model, error } = createOpenAiClient()
if (error) {
return new Response(
JSON.stringify({
error: error.message,
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
)
}
const body = await (request.json() as Promise<{
messages: { content: string; role: 'user' | 'assistant' }[]
@@ -48,7 +46,7 @@ async function handlePost(request: NextRequest) {
const { messages, existingSql, entityDefinitions } = body
try {
const stream = await generateV2(openai, messages, existingSql, entityDefinitions)
const stream = await generateV2(openai, model, messages, existingSql, entityDefinitions)
return new StreamingTextResponse(stream)
} catch (error) {
if (error instanceof Error) {
+8 -11
View File
@@ -1,18 +1,9 @@
import { ContextLengthError, EmptySqlError, generateSql } from 'ai-commands'
import { createOpenAiClient } from 'ai-commands/src/openai'
import apiWrapper from 'lib/api/apiWrapper'
import { NextApiRequest, NextApiResponse } from 'next'
import { OpenAI } from 'openai'
const openAiKey = process.env.OPENAI_KEY
const openai = new OpenAI({ apiKey: openAiKey })
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!openAiKey) {
return res.status(500).json({
error: 'No OPENAI_KEY set. Create this environment variable to use AI features.',
})
}
const { method } = req
switch (method) {
@@ -25,12 +16,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
export async function handlePost(req: NextApiRequest, res: NextApiResponse) {
const { openai, model, error } = createOpenAiClient()
if (error) {
return res.status(500).json({ error: error.message })
}
const {
body: { prompt, entityDefinitions },
} = req
try {
const result = await generateSql(openai, prompt, entityDefinitions)
const result = await generateSql(openai, model, prompt, entityDefinitions)
return res.json(result)
} catch (error) {
if (error instanceof Error) {
+15 -17
View File
@@ -1,25 +1,11 @@
import { StreamingTextResponse } from 'ai'
import { chatRlsPolicy } from 'ai-commands/edge'
import { createOpenAiClient } from 'ai-commands/src/openai'
import { NextRequest } from 'next/server'
import OpenAI from 'openai'
export const runtime = 'edge'
const openAiKey = process.env.OPENAI_KEY
export default async function handler(req: NextRequest) {
if (!openAiKey) {
return new Response(
JSON.stringify({
error: 'No OPENAI_KEY set. Create this environment variable to use AI features.',
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
)
}
const { method } = req
switch (method) {
@@ -37,7 +23,19 @@ export default async function handler(req: NextRequest) {
}
async function handlePost(request: NextRequest) {
const openai = new OpenAI({ apiKey: openAiKey })
const { openai, model, error } = createOpenAiClient()
if (error) {
return new Response(
JSON.stringify({
error: error.message,
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
)
}
const body = await (request.json() as Promise<{
messages: { content: string; role: 'user' | 'assistant' }[]
@@ -48,7 +46,7 @@ async function handlePost(request: NextRequest) {
const { messages, entityDefinitions, policyDefinition } = body
try {
const stream = await chatRlsPolicy(openai, messages, entityDefinitions, policyDefinition)
const stream = await chatRlsPolicy(openai, model, messages, entityDefinitions, policyDefinition)
return new StreamingTextResponse(stream)
} catch (error) {
console.error(error)
+8 -11
View File
@@ -1,18 +1,9 @@
import { ContextLengthError, titleSql } from 'ai-commands'
import { createOpenAiClient } from 'ai-commands/src/openai'
import apiWrapper from 'lib/api/apiWrapper'
import { NextApiRequest, NextApiResponse } from 'next'
import { OpenAI } from 'openai'
const openAiKey = process.env.OPENAI_KEY
const openai = new OpenAI({ apiKey: openAiKey })
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!openAiKey) {
return res.status(500).json({
error: 'No OPENAI_KEY set. Create this environment variable to use AI features.',
})
}
const { method } = req
switch (method) {
@@ -25,12 +16,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
export async function handlePost(req: NextApiRequest, res: NextApiResponse) {
const { openai, model, error } = createOpenAiClient()
if (error) {
return res.status(500).json({ error: error.message })
}
const {
body: { sql },
} = req
try {
const result = await titleSql(openai, sql)
const result = await titleSql(openai, model, sql)
return res.json(result)
} catch (error) {
if (error instanceof Error) {
+176 -41
View File
@@ -39,6 +39,7 @@
"@supabase/ssr": "^0.1.0",
"@tanstack/react-query": "^5.13.4",
"ai": "^2.2.31",
"ai-commands": "*",
"common": "*",
"config": "*",
"dayjs": "^1.11.10",
@@ -6983,6 +6984,14 @@
"react-hook-form": "^7.0.0"
}
},
"node_modules/@huggingface/jinja": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.2.2.tgz",
"integrity": "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==",
"engines": {
"node": ">=18"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@@ -8646,6 +8655,60 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"node_modules/@radix-ui/colors": {
"version": "0.1.9",
"license": "MIT"
@@ -17012,6 +17075,11 @@
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
"dev": true
},
"node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
},
"node_modules/@types/markdown-table": {
"version": "3.0.0",
"dev": true,
@@ -18041,6 +18109,19 @@
"@xtuc/long": "4.2.2"
}
},
"node_modules/@xenova/transformers": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.1.tgz",
"integrity": "sha512-zo702tQAFZXhzeD2GCYUNUqeqkoueOdiSbQWa4s0q7ZE4z8WBIwIsMMPGobpgdqjQ2u0Qulo08wuqVEUrBXjkQ==",
"dependencies": {
"@huggingface/jinja": "^0.2.2",
"onnxruntime-web": "1.14.0",
"sharp": "^0.32.0"
},
"optionalDependencies": {
"onnxruntime-node": "1.14.0"
}
},
"node_modules/@xobotyi/scrollbar-width": {
"version": "1.9.5",
"license": "MIT"
@@ -18898,8 +18979,7 @@
"node_modules/b4a": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz",
"integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==",
"dev": true
"integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg=="
},
"node_modules/babel-core": {
"version": "7.0.0-bridge.0",
@@ -19345,14 +19425,12 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.0.tgz",
"integrity": "sha512-Yyyqff4PIFfSuthCZqLlPISTWHmnQxoPuAvkmgzsJEmG3CesdIv6Xweayl0JkCZJSB2yYIdJyEz97tpxNhgjbg==",
"dev": true,
"optional": true
},
"node_modules/bare-fs": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.2.0.tgz",
"integrity": "sha512-+VhW202E9eTVGkX7p+TNXtZC4RTzj9JfJW7PtfIbZ7mIQ/QT9uOafQTx7lx2n9ERmWsXvLHF4hStAFn4gl2mQw==",
"dev": true,
"optional": true,
"dependencies": {
"bare-events": "^2.0.0",
@@ -19365,14 +19443,12 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.2.0.tgz",
"integrity": "sha512-hD0rOPfYWOMpVirTACt4/nK8mC55La12K5fY1ij8HAdfQakD62M+H4o4tpfKzVGLgRDTuk3vjA4GqGXXCeFbag==",
"dev": true,
"optional": true
},
"node_modules/bare-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.0.tgz",
"integrity": "sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw==",
"dev": true,
"optional": true,
"dependencies": {
"bare-os": "^2.1.0"
@@ -22207,7 +22283,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dev": true,
"dependencies": {
"mimic-response": "^3.1.0"
},
@@ -22280,7 +22355,6 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true,
"engines": {
"node": ">=4.0.0"
}
@@ -24336,7 +24410,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"dev": true,
"engines": {
"node": ">=6"
}
@@ -24533,8 +24606,7 @@
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"dev": true
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
},
"node_modules/fast-glob": {
"version": "3.3.2",
@@ -24889,6 +24961,11 @@
"node": ">=12.0.0"
}
},
"node_modules/flatbuffers": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz",
"integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ=="
},
"node_modules/flatted": {
"version": "3.2.9",
"license": "ISC"
@@ -25502,8 +25579,7 @@
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"dev": true
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
},
"node_modules/github-slugger": {
"version": "2.0.0",
@@ -25683,6 +25759,11 @@
"node": ">=6.0"
}
},
"node_modules/guid-typescript": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="
},
"node_modules/gulp-header": {
"version": "1.8.12",
"license": "MIT",
@@ -27100,8 +27181,7 @@
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"node_modules/ini-simple-parser": {
"version": "1.0.0",
@@ -30404,6 +30484,11 @@
"node": ">=8"
}
},
"node_modules/long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"node_modules/longest-streak": {
"version": "3.1.0",
"license": "MIT",
@@ -34588,7 +34673,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"dev": true,
"engines": {
"node": ">=10"
},
@@ -35015,8 +35099,7 @@
"node_modules/napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
"dev": true
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
},
"node_modules/natural-compare": {
"version": "1.4.0",
@@ -35197,7 +35280,6 @@
"version": "3.56.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.56.0.tgz",
"integrity": "sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==",
"dev": true,
"dependencies": {
"semver": "^7.3.5"
},
@@ -35209,7 +35291,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -35221,7 +35302,6 @@
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -35235,8 +35315,7 @@
"node_modules/node-abi/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/node-abort-controller": {
"version": "3.1.1",
@@ -36364,6 +36443,46 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/onnx-proto": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/onnx-proto/-/onnx-proto-4.0.4.tgz",
"integrity": "sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==",
"dependencies": {
"protobufjs": "^6.8.8"
}
},
"node_modules/onnxruntime-common": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz",
"integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew=="
},
"node_modules/onnxruntime-node": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz",
"integrity": "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==",
"optional": true,
"os": [
"win32",
"darwin",
"linux"
],
"dependencies": {
"onnxruntime-common": "~1.14.0"
}
},
"node_modules/onnxruntime-web": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz",
"integrity": "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==",
"dependencies": {
"flatbuffers": "^1.12.0",
"guid-typescript": "^1.0.9",
"long": "^4.0.0",
"onnx-proto": "^4.0.4",
"onnxruntime-common": "~1.14.0",
"platform": "^1.3.6"
}
},
"node_modules/open": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
@@ -37123,6 +37242,11 @@
"pathe": "^1.1.0"
}
},
"node_modules/platform": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="
},
"node_modules/playwright": {
"version": "1.41.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz",
@@ -37560,7 +37684,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
"dev": true,
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
@@ -37852,6 +37975,31 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/protobufjs": {
"version": "6.11.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz",
"integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",
"hasInstallScript": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/long": "^4.0.1",
"@types/node": ">=13.7.0",
"long": "^4.0.0"
},
"bin": {
"pbjs": "bin/pbjs",
"pbts": "bin/pbts"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"dev": true,
@@ -38132,8 +38280,7 @@
"node_modules/queue-tick": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
"dev": true
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
},
"node_modules/raf-schd": {
"version": "4.0.3",
@@ -38246,7 +38393,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dev": true,
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@@ -38261,7 +38407,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -41423,7 +41568,6 @@
"version": "0.32.6",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz",
"integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"color": "^4.2.3",
@@ -41446,7 +41590,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -41457,14 +41600,12 @@
"node_modules/sharp/node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
"dev": true
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
},
"node_modules/sharp/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -41479,7 +41620,6 @@
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz",
"integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==",
"dev": true,
"dependencies": {
"pump": "^3.0.0",
"tar-stream": "^3.1.5"
@@ -41493,7 +41633,6 @@
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
"integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
"dev": true,
"dependencies": {
"b4a": "^1.6.4",
"fast-fifo": "^1.2.0",
@@ -41503,8 +41642,7 @@
"node_modules/sharp/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/shebang-command": {
"version": "2.0.0",
@@ -41619,7 +41757,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"dev": true,
"funding": [
{
"type": "github",
@@ -41639,7 +41776,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -42301,7 +42437,6 @@
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz",
"integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==",
"dev": true,
"dependencies": {
"fast-fifo": "^1.1.0",
"queue-tick": "^1.0.1"
@@ -44445,7 +44580,6 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.0.1"
},
@@ -47343,6 +47477,7 @@
"@gregnr/libpg-query": "^13.4.0-dev.12",
"@serafin/schema-builder": "^0.18.5",
"@supabase/supabase-js": "*",
"@xenova/transformers": "^2.17.1",
"ai": "^2.2.29",
"common-tags": "^1.8.2",
"config": "*",
+2
View File
@@ -1,2 +1,4 @@
export * from './src/errors'
export * from './src/openai'
export * from './src/sql'
export * from './src/tokenizer'
+1 -1
View File
@@ -10,6 +10,6 @@ module.exports = {
setupFilesAfterEnv: ['./test/extensions.ts'],
testTimeout: 15000,
transformIgnorePatterns: [
'node_modules/(?!(mdast-.*|micromark|micromark-.*|unist-.*|decode-named-character-reference|character-entities)/)',
'node_modules/(?!(mdast-.*|micromark|micromark-.*|unist-.*|decode-named-character-reference|character-entities|@xenova/transformers)/)',
],
}
+1
View File
@@ -12,6 +12,7 @@
"@gregnr/libpg-query": "^13.4.0-dev.12",
"@serafin/schema-builder": "^0.18.5",
"@supabase/supabase-js": "*",
"@xenova/transformers": "^2.17.1",
"ai": "^2.2.29",
"common-tags": "^1.8.2",
"config": "*",
@@ -32,9 +32,9 @@ exports[`edit add length constraint 1`] = `
exports[`generate single table with specified columns 1`] = `
"create table employees (
id bigint primary key generated always as identity,
name text,
email text,
position text
name text not null,
email text unique not null,
position text not null
);"
`;
@@ -46,3 +46,25 @@ exports[`rls chat select policy using table definition 1`] = `
"usingExpression": "user_id = auth.uid ()",
}
`;
exports[`sql chat single table with specified columns 1`] = `
"create table employees (
id bigint primary key generated always as identity,
name text not null,
email text unique not null,
position text not null,
created_at timestamp with time zone default current_timestamp,
updated_at timestamp with time zone default current_timestamp
);"
`;
exports[`sql chat table referencing users creates fk to auth.users 1`] = `
"create table todos (
id bigint primary key generated always as identity,
title text not null,
description text,
created_at timestamp with time zone default current_timestamp,
author_id uuid references auth.users (id),
completed boolean default false
);"
`;
+129
View File
@@ -0,0 +1,129 @@
import OpenAI from 'openai'
export const supportedModels = [
'llama3',
'llama3-70b-8192',
'llama3-8b-8192',
'gpt-4-turbo',
'gpt-4-turbo-preview',
'gpt-4-turbo-2024-04-09',
'gpt-4-0125-preview',
'gpt-4-1106-preview',
'gpt-4',
'gpt-4-0613',
'gpt-4-0314',
'gpt-3.5-turbo',
'gpt-3.5-turbo-0125',
'gpt-3.5-turbo-1106',
]
export const supportedEmbeddingModels = ['text-embedding-ada-002']
/**
* Instantiates OpenAI client while checking and validating
* environment variables.
*
* Allows you to use your own OpenAI-compatible provider.
*
* Uses the following env vars:
* - OPENAI_API_KEY (backwards compatible with previous OPENAI_KEY)
* - OPENAI_BASE_URL
* - OPENAI_MODEL
*/
export function createOpenAiClient() {
const apiKey = process.env.OPENAI_API_KEY ?? process.env.OPENAI_KEY
const baseURL = process.env.OPENAI_BASE_URL
const model = process.env.OPENAI_MODEL
if (!apiKey) {
return {
error: new Error(
'No OPENAI_API_KEY set. Create this environment variable to use AI features.'
),
}
}
if (!baseURL) {
return {
error: new Error(
'No OPENAI_BASE_URL set. Create this environment variable to use AI features.'
),
}
}
if (!model) {
return {
error: new Error('No OPENAI_MODEL set. Create this environment variable to use AI features.'),
}
}
if (!supportedModels.includes(model)) {
return {
error: new Error(
`The following models are supported for OPENAI_MODEL: ${JSON.stringify(supportedModels)}`
),
}
}
const openai = new OpenAI({ apiKey, baseURL })
return { openai, model }
}
/**
* Instantiates OpenAI client for embedding models while checking and validating
* environment variables.
*
* Allows you to use separate OpenAI-compatible providers for embedding models vs. language models.
*
* Uses the following env vars:
* - OPENAI_EMBEDDING_API_KEY
* - OPENAI_EMBEDDING_BASE_URL
* - OPENAI_EMBEDDING_MODEL
*
* Falls back to these default env vars if the above vars don't exist:
* - OPENAI_API_KEY
* - OPENAI_BASE_URL
*/
export function createOpenAiEmbeddingClient() {
const apiKey =
process.env.OPENAI_EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY ?? process.env.OPENAI_KEY
const baseURL = process.env.OPENAI_EMBEDDING_BASE_URL ?? process.env.OPENAI_BASE_URL
const model = process.env.OPENAI_EMBEDDING_MODEL
if (!apiKey) {
return {
error: new Error(
'No OPENAI_EMBEDDING_API_KEY set. Create this environment variable to use AI embedding features.'
),
}
}
if (!baseURL) {
return {
error: new Error(
'No OPENAI_EMBEDDING_BASE_URL set. Create this environment variable to use AI embedding features.'
),
}
}
if (!model) {
return {
error: new Error(
'No OPENAI_EMBEDDING_MODEL set. Create this environment variable to use AI embedding features.'
),
}
}
if (!supportedEmbeddingModels.includes(model)) {
return {
error: new Error(
`The following models are supported for OPENAI_EMBEDDING_MODEL: ${JSON.stringify(supportedEmbeddingModels)}`
),
}
}
const openai = new OpenAI({ apiKey, baseURL })
return { openai, model }
}
+36 -71
View File
@@ -3,13 +3,8 @@ import { OpenAIStream } from 'ai'
import { codeBlock, oneLine, stripIndent } from 'common-tags'
import OpenAI from 'openai'
import { ApplicationError, ContextLengthError, UserError } from './errors'
import { getChatRequestTokenCount, getMaxTokenCount, tokenizer } from './tokenizer'
interface Message {
role: 'user' | 'assistant'
content: string
}
import { ApplicationError, ContextLengthError } from './errors'
import { Message, capMessages, countTokens } from './tokenizer'
/**
* Responds to a conversation about building an RLS policy.
@@ -18,6 +13,7 @@ interface Message {
*/
export async function chatRlsPolicy(
openai: OpenAI,
model: string,
messages: Message[],
entityDefinitions?: string[],
policyDefinition?: string
@@ -26,7 +22,7 @@ export async function chatRlsPolicy(
{
role: 'system',
content: stripIndent`
You're an Postgres expert in writing row level security policies. Your purpose is to
You're a Postgres expert in writing row level security policies. Your purpose is to
generate a policy with the constraints given by the user. You will be provided a schema
on which the policy should be applied.
@@ -35,18 +31,18 @@ export async function chatRlsPolicy(
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- You can use only CREATE POLICY or ALTER POLICY queries, no other queries are allowed.
- You can add short explanations to your messages.
- The result should be a valid markdown. The SQL code should be wrapped in \`\`\`.
- The result should be a valid markdown. The SQL code should be wrapped in \`\`\` (including sql language tag).
- Always use "auth.uid()" instead of "current_user".
- You can't use "USING" expression on INSERT policies.
- Only use "WITH CHECK" expression on INSERT or UPDATE policies.
- The policy name should be short text explaining the policy, enclosed in double quotes.
- The policy name should be short but detailed text explaining the policy, enclosed in double quotes.
- Always put explanations as separate text. Never use inline SQL comments.
- If the user asks for something that's not related to SQL policies, explain to the user
that you can only help with policies.
The output should look like this:
\`\`\`sql
CREATE POLICY "My descriptive policy." ON users FOR INSERT USING (user_name = current_user) WITH (true);
CREATE POLICY "My descriptive policy." ON books FOR INSERT USING (author_id = auth.uid()) WITH (true);
\`\`\`
`,
},
@@ -77,7 +73,7 @@ export async function chatRlsPolicy(
try {
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo-0125',
model,
messages: initMessages,
max_tokens: 1024,
temperature: 0,
@@ -101,24 +97,37 @@ export async function chatRlsPolicy(
*/
export async function generateV2(
openai: OpenAI,
model: string,
messages: Message[],
existingSql?: string,
entityDefinitions?: string[]
): Promise<ReadableStream<Uint8Array>> {
const initMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
const initMessages: Message[] = [
{
role: 'system',
content: stripIndent`
You are a Supabase Postgres SQL generator. The user will have a conversation with you and your job is to generate SQL
in the Postgres dialect. Be friendly and relatable, complementing the user when appropriate, but don't go overboard.
The generated SQL (must be valid SQL).
- For primary keys, always use "id bigint primary key generated always as identity" (not serial)
- Prefer creating foreign key references in the create statement
- When creating new tables, always add foreign key references inline (ie. "my_column my_type references ..." on a single line)
- Include all necessary tables and create them in the correct order (eg. book references author, so create author before book)
- Prefer 'text' over 'varchar'
- Prefer 'timestamp with time zone' over 'date'
- Use vector(384) data type for any embedding/vector related query
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- Always use semicolons
- There is a built-in auth.users table with a uuid id column (but it's immutable)
- Prefer referencing auth.users directly via FK instead of creating a new users table
- Only create your own users table if you need to store additional data about the user such as avatar
- If you do create your own users table, prefer to call it "profiles" and add a FK to auth.users. You must also create a trigger function
that automatically creates the profile when there is a new auth.user
- Sprinkle in detailed explanations throughout your answer, digging into "why" each snippet is necessary
- Explanations should live outside of code blocks
- Output as markdown
- Always include code snippets if available
- Always include code snippets if available (including sql language tag, ie. \`\`\`sql\n...\n\`\`\`)
`,
},
]
@@ -148,7 +157,7 @@ export async function generateV2(
try {
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo-0125',
model,
messages: initMessages,
max_tokens: 1024,
temperature: 0,
@@ -167,6 +176,9 @@ export async function generateV2(
export async function clippy(
openai: OpenAI,
openaiEmbedding: OpenAI,
model: string,
embeddingModel: string,
supabaseClient: SupabaseClient<any, 'public', any>,
messages: Message[]
) {
@@ -188,25 +200,9 @@ export async function clippy(
throw new Error("No message with role 'user'")
}
// Moderate the content to comply with OpenAI T&C
const moderationResponses = await Promise.all(
contextMessages.map((message) => openai.moderations.create({ input: message.content }))
)
for (const moderationResponse of moderationResponses) {
const [results] = moderationResponse.results
if (results.flagged) {
throw new UserError('Flagged content', {
flagged: true,
categories: results.categories,
})
}
}
const embeddingResponse = await openai.embeddings
const embeddingResponse = await openaiEmbedding.embeddings
.create({
model: 'text-embedding-ada-002',
model: embeddingModel,
input: userMessage.content.replaceAll('\n', ' '),
})
.catch((error: any) => {
@@ -235,8 +231,7 @@ export async function clippy(
for (let i = 0; i < pageSections.length; i++) {
const pageSection = pageSections[i]
const content = pageSection.content
const encoded = tokenizer.encode(content)
tokenCount += encoded.length
tokenCount += await countTokens(content, model)
if (tokenCount >= 1500) {
break
@@ -245,7 +240,7 @@ export async function clippy(
contextText += `${content.trim()}\n---\n`
}
const initMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
const initMessages: Message[] = [
{
role: 'system',
content: codeBlock`
@@ -307,20 +302,19 @@ export async function clippy(
},
]
const model = 'gpt-3.5-turbo-0301'
const maxCompletionTokenCount = 1024
const maxCompletionTokens = 1024
const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = capMessages(
const completionMessages: Message[] = await capMessages(
initMessages,
contextMessages,
maxCompletionTokenCount,
model
model,
maxCompletionTokens
)
const completionOptions = {
model,
messages: completionMessages,
max_tokens: 1024,
max_tokens: maxCompletionTokens,
temperature: 0,
stream: true,
}
@@ -342,32 +336,3 @@ export async function clippy(
return response
}
/**
* Remove context messages until the entire request fits
* the max total token count for that model.
*
* Accounts for both message and completion token counts.
*/
function capMessages(
initMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
contextMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
maxCompletionTokenCount: number,
model: string
) {
const maxTotalTokenCount = getMaxTokenCount(model)
const cappedContextMessages = [...contextMessages]
let tokenCount =
getChatRequestTokenCount([...initMessages, ...cappedContextMessages], model) +
maxCompletionTokenCount
// Remove earlier context messages until we fit
while (tokenCount >= maxTotalTokenCount) {
cappedContextMessages.shift()
tokenCount =
getChatRequestTokenCount([...initMessages, ...cappedContextMessages], model) +
maxCompletionTokenCount
}
return [...initMessages, ...cappedContextMessages]
}
+44 -4
View File
@@ -1,17 +1,23 @@
import { describe, expect, test } from '@jest/globals'
import { codeBlock } from 'common-tags'
import OpenAI from 'openai'
import { collectStream, extractMarkdownSql, formatSql, getPolicyInfo } from '../test/util'
import { createOpenAiClient } from './openai'
import { debugSql, editSql, generateSql, titleSql } from './sql'
import { chatRlsPolicy } from './sql.edge'
import { chatRlsPolicy, generateV2 } from './sql.edge'
const openAiKey = process.env.OPENAI_KEY
const openai = new OpenAI({ apiKey: openAiKey })
const { openai, model, error } = createOpenAiClient()
if (error) {
throw error
}
console.info(`Running AI tests against model '${model}'`)
describe('generate', () => {
test('single table with specified columns', async () => {
const { sql, title } = await generateSql(
openai,
model,
'create a table to track employees with name, email, and position'
)
@@ -24,6 +30,7 @@ describe('edit', () => {
test('add length constraint', async () => {
const { sql } = await editSql(
openai,
model,
'force name to be at least 4 characters',
codeBlock`
create table employees (
@@ -42,6 +49,7 @@ describe('debug', () => {
test('fix order of operations', async () => {
const { sql } = await debugSql(
openai,
model,
'relation "departments" does not exist',
codeBlock`
create table employees (
@@ -64,6 +72,7 @@ describe('debug', () => {
test('fix typos', async () => {
const { sql, solution } = await debugSql(
openai,
model,
'syntax error at or near "fromm"',
codeBlock`
select * fromm employees;
@@ -79,6 +88,7 @@ describe('title', () => {
test('title matches content', async () => {
const { title, description } = await titleSql(
openai,
model,
codeBlock`
create table employees (
id bigint primary key generated always as identity,
@@ -103,6 +113,7 @@ describe('rls chat', () => {
test('select policy using table definition', async () => {
const responseStream = await chatRlsPolicy(
openai,
model,
[
{
role: 'user',
@@ -131,3 +142,32 @@ describe('rls chat', () => {
)
})
})
describe('sql chat', () => {
test('single table with specified columns', async () => {
const responseStream = await generateV2(openai, model, [
{
role: 'user',
content: 'create a table to track employees with name, email, and position',
},
])
const responseText = await collectStream(responseStream)
const [sql] = extractMarkdownSql(responseText)
expect(sql).toMatchSnapshot()
})
test('table referencing users creates fk to auth.users', async () => {
const responseStream = await generateV2(openai, model, [
{
role: 'user',
content: 'create a todos table and track author',
},
])
const responseText = await collectStream(responseStream)
const [sql] = extractMarkdownSql(responseText)
expect(sql).toMatchSnapshot()
})
})
+93 -47
View File
@@ -9,13 +9,6 @@ const generateSqlSchema = SchemaBuilder.emptySchema()
.addString('sql', {
description: stripIndent`
The generated SQL (must be valid SQL).
- For primary keys, always use "id bigint primary key generated always as identity" (not serial)
- Prefer creating foreign key references in the create statement
- Prefer 'text' over 'varchar'
- Prefer 'timestamp with time zone' over 'date'
- Use vector(384) data type for any embedding/vector related query
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- Always use semicolons
`,
})
.addString('title', {
@@ -28,16 +21,6 @@ const generateSqlSchema = SchemaBuilder.emptySchema()
const editSqlSchema = SchemaBuilder.emptySchema().addString('sql', {
description: stripIndent`
The modified SQL (must be valid SQL).
- Assume the query hasn't been executed yet
- For primary keys, always use "id bigint primary key generated always as identity" (not serial)
- When creating tables, always add foreign key references inline
- Prefer 'text' over 'varchar'
- Prefer 'timestamp with time zone' over 'date'
- Use vector(384) data type for any embedding/vector related query
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- Use real examples when possible
- Add constraints if requested
- Always use semicolons
`,
})
@@ -83,28 +66,12 @@ const completionFunctions = {
},
debugSql: {
name: 'debugSql',
description: stripIndent`
Debugs a Postgres SQL error. Returns the fixed SQL and a solution explaining it.
- Create extensions if they are missing (only for valid extensions)
- Suggest creating tables if they are missing
- Include all of the original SQL
- For primary keys, always use "id bigint primary key generated always as identity" (not serial)
- When creating tables, always add foreign key references inline
- Prefer 'text' over 'varchar'
- Prefer 'timestamp with time zone' over 'date'
- Use vector(384) data type for any embedding/vector related query
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- Always use semicolons
`,
description: 'Debugs a Postgres SQL error. Returns the fixed SQL and a solution explaining it.',
parameters: debugSqlSchema.schema as Record<string, unknown>,
},
generateTitle: {
name: 'generateTitle',
description: stripIndent`
Generates a short title and summarized description for a Postgres SQL snippet.
The description should describe why this table was created (eg. "Table to track todos")
`,
description: 'Generates a short title and summarized description for a Postgres SQL snippet.',
parameters: generateTitleSchema.schema as Record<string, unknown>,
},
} satisfies Record<string, OpenAI.Chat.Completions.ChatCompletionCreateParams.Function>
@@ -114,11 +81,36 @@ const completionFunctions = {
*
* @returns The generated SQL along with a title for it.
*/
export async function generateSql(openai: OpenAI, prompt: string, entityDefinitions?: string[]) {
export async function generateSql(
openai: OpenAI,
model: string,
prompt: string,
entityDefinitions?: string[]
) {
const hasEntityDefinitions = entityDefinitions !== undefined && entityDefinitions.length > 0
const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = []
completionMessages.push({
role: 'system',
content: stripIndent`
You are a Postgres SQL generator. You must generate Postgres SQL based on a natural language prompt
The generated SQL (must be valid SQL).
- For primary keys, always use "id bigint primary key generated always as identity" (not serial)
- Prefer creating foreign key references in the create statement
- When creating new tables, always add foreign key references inline (ie. "my_column my_type references ..." on a single line)
- Include all necessary tables and create them in the correct order (eg. book references author, so create author before book)
- Prefer 'text' over 'varchar'
- Prefer 'timestamp with time zone' over 'date'
- Use vector(384) data type for any embedding/vector related query
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- Always use semicolons
Response must contain only JSON satisfying the 'tool_calls'.
`,
})
if (hasEntityDefinitions) {
completionMessages.push({
role: 'user',
@@ -136,7 +128,7 @@ export async function generateSql(openai: OpenAI, prompt: string, entityDefiniti
try {
const completionResponse = await openai.chat.completions.create({
model: 'gpt-3.5-turbo-0125',
model,
messages: completionMessages,
max_tokens: 1024,
temperature: 0,
@@ -188,6 +180,7 @@ export async function generateSql(openai: OpenAI, prompt: string, entityDefiniti
*/
export async function editSql(
openai: OpenAI,
model: string,
prompt: string,
sql: string,
entityDefinitions?: string[]
@@ -196,6 +189,27 @@ export async function editSql(
const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = []
completionMessages.push({
role: 'system',
content: stripIndent`
You are a Postgres SQL editor. You must edit Postgres SQL based on the user's instructions.
- Assume the query hasn't been executed yet
- For primary keys, always use "id bigint primary key generated always as identity" (not serial)
- When creating new tables, always add foreign key references inline (ie. "my_column my_type references ..." on a single line)
- Include all necessary tables and create them in the correct order (eg. book references author, so create author before book)
- Prefer 'text' over 'varchar'
- Prefer 'timestamp with time zone' over 'date'
- Use vector(384) data type for any embedding/vector related query
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- Use real examples when possible
- Add constraints if requested
- Always use semicolons
Response must contain only JSON satisfying the 'tool_calls'.
`,
})
if (hasEntityDefinitions) {
completionMessages.push({
role: 'user',
@@ -222,7 +236,7 @@ export async function editSql(
try {
const completionResponse = await openai.chat.completions.create({
model: 'gpt-3.5-turbo-0125',
model,
messages: completionMessages,
max_tokens: 2048,
temperature: 0,
@@ -274,6 +288,7 @@ export async function editSql(
*/
export async function debugSql(
openai: OpenAI,
model: string,
errorMessage: string,
sql: string,
entityDefinitions?: string[]
@@ -282,6 +297,25 @@ export async function debugSql(
const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = []
completionMessages.push({
role: 'system',
content: stripIndent`
You are a Postgres SQL debugger. You must fix the user's SQL and a provide solution explaining it.
- Create extensions if they are missing (only for valid extensions)
- Suggest creating tables if they are missing
- Include all of the original SQL
- For primary keys, always use "id bigint primary key generated always as identity" (not serial)
- When creating tables, always add foreign key references inline
- Prefer 'text' over 'varchar'
- Prefer 'timestamp with time zone' over 'date'
- Use vector(384) data type for any embedding/vector related query
- Always use double apostrophe in SQL strings (eg. 'Night''s watch')
- Always use semicolons
- Do not mix up the user's error with "tool use failed"
`,
})
if (hasEntityDefinitions) {
completionMessages.push({
role: 'user',
@@ -311,7 +345,7 @@ export async function debugSql(
try {
const completionResponse = await openai.chat.completions.create({
model: 'gpt-3.5-turbo-0125',
model,
messages: completionMessages,
max_tokens: 2048,
temperature: 0,
@@ -361,17 +395,29 @@ export async function debugSql(
*
* @returns A title and description for the SQL snippet.
*/
export async function titleSql(openai: OpenAI, sql: string) {
const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
{
role: 'user',
content: sql,
},
]
export async function titleSql(openai: OpenAI, model: string, sql: string) {
const completionMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = []
completionMessages.push({
role: 'system',
content: stripIndent`
You are a Postgres SQL title/description generator.
You must generates a short title and summarized description for a Postgres SQL snippet.
- The title should be short but still address all parts of the code.
- The description should describe why this table was created (eg. "Table to track todos")
while addressing all parts of the code.
`,
})
completionMessages.push({
role: 'user',
content: sql,
})
try {
const completionResponse = await openai.chat.completions.create({
model: 'gpt-3.5-turbo-0125',
model,
messages: completionMessages,
max_tokens: 1024,
temperature: 0,
@@ -0,0 +1,24 @@
import { describe, expect, test } from '@jest/globals'
import { Message, tokenizeChat } from './tokenizer'
const sampleMessages: Message[] = [
{ role: 'system', content: 'You are a helpful Postgres SQL assistant' },
{ role: 'user', content: 'Help me write SQL to get all movies that start with T' },
{
role: 'assistant',
content: "Sure!\n```sql\nselect * from movies where title like 'T%';\n```",
},
{ role: 'user', content: 'Thanks! What about S?' },
]
describe('tokenizer', () => {
test('counts llama3 tokens correctly', async () => {
const tokens = await tokenizeChat(sampleMessages, 'llama3')
expect(tokens.length).toBe(68)
})
test('counts gpt-3.5-turbo tokens correctly', async () => {
const tokens = await tokenizeChat(sampleMessages, 'gpt-3.5-turbo')
expect(tokens.length).toBe(66)
})
})
+176 -73
View File
@@ -1,88 +1,191 @@
import { getEncoding } from 'js-tiktoken'
import OpenAI from 'openai'
import { AutoTokenizer, PreTrainedTokenizer } from '@xenova/transformers'
export const tokenizer = getEncoding('cl100k_base')
export interface Message {
role: 'system' | 'user' | 'assistant'
content: string
}
/**
* Count the tokens for multi-message chat completion requests
*/
export function getChatRequestTokenCount(
messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
model = 'gpt-3.5-turbo-0301'
): number {
const tokensPerRequest = 3 // every reply is primed with <|im_start|>assistant<|im_sep|>
const numTokens = messages.reduce((acc, message) => acc + getMessageTokenCount(message, model), 0)
return numTokens + tokensPerRequest
function isNumberArray(value: any): value is number[] {
return Array.isArray(value) && typeof value[0] === 'number'
}
/**
* Count the tokens for a single message within a chat completion request
*
* See "Counting tokens for chat API calls"
* from https://github.com/openai/openai-cookbook/blob/834181d5739740eb8380096dac7056c925578d9a/examples/How_to_count_tokens_with_tiktoken.ipynb
* Gets the Hugging Face model path for the given model.
*/
export function getMessageTokenCount(
message: OpenAI.Chat.Completions.ChatCompletionMessageParam,
model = 'gpt-3.5-turbo-0301'
): number {
let tokensPerMessage: number
let tokensPerName: number
function getModelPath(model: string) {
switch (model) {
case 'gpt-3.5-turbo':
console.warn(
'Warning: gpt-3.5-turbo may change over time. Returning num tokens assuming gpt-3.5-turbo-0301.'
)
return getMessageTokenCount(message, 'gpt-3.5-turbo-0301')
case 'llama3':
case 'llama3-70b-8192':
case 'llama3-8b-8192':
return 'Xenova/llama-3-tokenizer'
case 'gpt-4-turbo':
case 'gpt-4-turbo-preview':
case 'gpt-4-turbo-2024-04-09':
case 'gpt-4-0125-preview':
case 'gpt-4-1106-preview':
case 'gpt-4-vision-preview':
case 'gpt-4-1106-vision-preview':
case 'gpt-4-32k':
case 'gpt-4-32k-0613':
case 'gpt-4':
console.warn('Warning: gpt-4 may change over time. Returning num tokens assuming gpt-4-0314.')
return getMessageTokenCount(message, 'gpt-4-0314')
case 'gpt-3.5-turbo-0301':
tokensPerMessage = 4 // every message follows <|start|>{role/name}\n{content}<|end|>\n
tokensPerName = -1 // if there's a name, the role is omitted
break
case 'gpt-4-0613':
case 'gpt-4-0314':
tokensPerMessage = 3
tokensPerName = 1
break
default:
throw new Error(
`Unknown model '${model}'. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.`
)
}
return Object.entries(message).reduce((acc, [key, value]) => {
acc += tokenizer.encode(value).length
if (key === 'name') {
acc += tokensPerName
}
return acc
}, tokensPerMessage)
}
/**
* Get the maximum number of tokens for a model's context.
*
* Includes tokens in both message and completion.
*/
export function getMaxTokenCount(model: string): number {
switch (model) {
case 'gpt-3.5-turbo':
console.warn(
'Warning: gpt-3.5-turbo may change over time. Returning max num tokens assuming gpt-3.5-turbo-0301.'
)
return getMaxTokenCount('gpt-3.5-turbo-0301')
case 'gpt-4':
console.warn(
'Warning: gpt-4 may change over time. Returning max num tokens assuming gpt-4-0314.'
)
return getMaxTokenCount('gpt-4-0314')
case 'gpt-3.5-turbo-0125':
case 'gpt-3.5-turbo-1106':
case 'gpt-3.5-turbo-16k':
case 'gpt-3.5-turbo-16k-0613':
case 'gpt-3.5-turbo-instruct':
case 'gpt-3.5-turbo-0613':
case 'gpt-3.5-turbo-0301':
return 4097
case 'gpt-4-0314':
return 4097
return 'Xenova/gpt-4'
default:
throw new Error(`Unknown model '${model}'`)
}
}
const tokenizers = new Map<string, PreTrainedTokenizer>()
/**
* Creates a tokenizer for the specified model using
* Transformers.js and config downloaded from Hugging Face.
*
* Caches the tokenizer in memory for subsequent uses.
*/
async function getTokenizer(model: string) {
const cachedTokenizer = tokenizers.get(model)
if (cachedTokenizer) {
return cachedTokenizer
}
const modelPath = getModelPath(model)
const tokenizer = await AutoTokenizer.from_pretrained(modelPath)
tokenizers.set(model, tokenizer)
return tokenizer
}
/**
* Tokenizes plain text for the specified model.
*/
export async function tokenize(content: string, model: string) {
const tokenizer = await getTokenizer(model)
const tokens = tokenizer.encode(content)
return tokens
}
/**
* Tokenizes chat messages for the specified model.
*
* Includes delimiter tokens like <|im_start|>, <|im_end|>, etc
*/
export async function tokenizeChat(messages: Message[], model: string) {
const tokenizer = await getTokenizer(model)
const tokens = tokenizer.apply_chat_template(messages, {
tokenize: true,
return_tensor: false,
add_generation_prompt: true,
})
if (!isNumberArray(tokens)) {
throw new Error(`Unexpected tokenizer output. Expected number array.`)
}
return tokens
}
/**
* Counts the number of tokens in plain text for the specified model.
*/
export async function countTokens(content: string, model: string) {
const tokens = await tokenize(content, model)
return tokens.length
}
/**
* Counts the number of tokens in the chat messages for the specified model.
*
* Accounts for delimiter tokens like <|im_start|>, <|im_end|>, etc
*/
export async function countChatTokens(messages: Message[], model: string) {
const tokens = await tokenizeChat(messages, model)
return tokens.length
}
/**
* Get the maximum number of tokens in a model's context window.
*
* Defaults to 4096 for unknown models.
*/
export function getContextWindow(model: string): number {
switch (model) {
case 'gpt-4-turbo':
case 'gpt-4-turbo-preview':
case 'gpt-4-turbo-2024-04-09':
case 'gpt-4-0125-preview':
case 'gpt-4-1106-preview':
case 'gpt-4-vision-preview':
case 'gpt-4-1106-vision-preview':
return 128000
case 'gpt-4-32k':
case 'gpt-4-32k-0613':
return 32768
case 'gpt-3.5-turbo':
case 'gpt-3.5-turbo-0125':
case 'gpt-3.5-turbo-1106':
case 'gpt-3.5-turbo-16k':
case 'gpt-3.5-turbo-16k-0613':
return 16385
case 'gpt-4':
case 'gpt-4-0613':
case 'llama3':
case 'llama3-70b-8192':
case 'llama3-8b-8192':
return 8192
case 'gpt-4-0314':
case 'gpt-3.5-turbo-instruct':
case 'gpt-3.5-turbo-0613':
case 'gpt-3.5-turbo-0301':
default:
return 4096
}
}
/**
* Remove conversation messages as needed until the entire
* request fits the context window of the specified model.
*
* @param systemMessages The initial system messages that must always exist
* @param conversationMessages The conversation messages that can be trimmed if necessary
* @param model The model that will be responding to these messages
* @param maxCompletionTokens The number of response tokens to leave room for
*
* @returns The final set of messages that will fit in the context window of the model.
*/
export async function capMessages(
systemMessages: Message[],
conversationMessages: Message[],
model: string,
maxCompletionTokens = 0
) {
const contextWindow = getContextWindow(model)
const trimmedConversationMessages = [...conversationMessages]
let tokenCount = await countChatTokens([...systemMessages, ...conversationMessages], model)
// Remove earlier conversation messages until we fit
while (tokenCount + maxCompletionTokens >= contextWindow) {
trimmedConversationMessages.shift()
tokenCount = await countChatTokens([...systemMessages, ...conversationMessages], model)
}
return [...systemMessages, ...trimmedConversationMessages]
}
+8 -5
View File
@@ -1,14 +1,15 @@
import { expect } from '@jest/globals'
import { codeBlock } from 'common-tags'
import OpenAI from 'openai'
import { createOpenAiClient } from '../src/openai'
const openAiKey = process.env.OPENAI_KEY
const openai = new OpenAI({ apiKey: openAiKey })
const { openai, model, error } = createOpenAiClient()
if (error) {
throw error
}
expect.extend({
async toMatchCriteria(received: string, criteria: string) {
const model = 'gpt-4-1106-preview'
const completionResponse = await openai.chat.completions.create({
model,
messages: [
@@ -21,7 +22,9 @@ expect.extend({
- \`{ "pass": true, "reason": "<reason>" }\` if 'Received' adheres to the test 'Criteria'
- \`{ "pass": false, "reason": "<reason>" }\` if 'Received' does not adhere to the test 'Criteria'
Be critical and make sure every part of 'Criteria' is addressed in 'Received'.
The "reason" must explain exactly which part of 'Received' did or did not pass the test 'Criteria'.
Be detailed as to why you failed the test.
`,
},
{