From b98eb9b7299e054a70c32f1ab681a48349e0557d Mon Sep 17 00:00:00 2001 From: arcticfly <41524992+arcticfly@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:38:46 -0600 Subject: [PATCH] Trigger llm output retrieval on server (#39) * Rename tables, add graphile workers, update types * Add dev:worker command * Update pnpm-lock.yaml * Remove sentry config import from worker.ts * Stop generating new cells in cell router get query * Generate new cells for new scenarios, variants, and experiments * Remove most error throwing from queryLLM.task.ts * Remove promptVariantId and testScenarioId from ModelOutput * Remove duplicate index from ModelOutput * Move inputHash from cell to output * Add TODO * Add todo * Show cost and time for each cell * Always show output stats if there is output * Trigger LLM outputs when scenario variables are updated * Add newlines to ends of files * Add another newline * Cascade ModelOutput deletion * Fix linting and prettier * Return instead of throwing for non-pending cell * Remove pnpm dev:worker from pnpm:dev * Update pnpm-lock.yaml --- package.json | 2 + pnpm-lock.yaml | 514 +++++++++++++----- .../migration.sql | 49 ++ .../migration.sql | 2 + prisma/schema.prisma | 49 +- prisma/seed.ts | 2 +- .../OutputsTable/OutputCell/CellOptions.tsx | 33 ++ .../OutputsTable/OutputCell/ErrorHandler.tsx | 54 +- .../OutputsTable/OutputCell/OutputCell.tsx | 140 +++-- .../OutputsTable/OutputCell/OutputStats.tsx | 6 +- src/pages/index.tsx | 2 +- src/server/api/autogen.ts | 4 - src/server/api/root.router.ts | 4 +- src/server/api/routers/experiments.router.ts | 5 +- src/server/api/routers/modelOutputs.router.ts | 101 ---- .../api/routers/promptVariants.router.ts | 36 +- .../routers/scenarioVariantCells.router.ts | 68 +++ src/server/api/routers/scenarios.router.ts | 25 +- .../migrateScenarioVariantOutputData.ts | 47 ++ src/server/tasks/defineTask.ts | 31 ++ src/server/tasks/queryLLM.task.ts | 144 +++++ src/server/tasks/worker.ts | 40 ++ src/server/utils/evaluations.ts | 28 +- src/server/utils/generateNewCell.ts | 76 +++ src/server/utils/getCompletion.ts | 2 +- src/server/utils/queueLLMRetrievalTask.ts | 22 + src/server/utils/shouldStream.ts | 7 + src/server/utils/sleep.ts | 1 + src/utils/useSocket.ts | 2 +- 29 files changed, 1089 insertions(+), 407 deletions(-) create mode 100644 prisma/migrations/20230714113205_create_scenariotvariant/migration.sql create mode 100644 prisma/migrations/20230714182259_add_model_to_variant/migration.sql create mode 100644 src/components/OutputsTable/OutputCell/CellOptions.tsx delete mode 100644 src/server/api/routers/modelOutputs.router.ts create mode 100644 src/server/api/routers/scenarioVariantCells.router.ts create mode 100644 src/server/scripts/migrateScenarioVariantOutputData.ts create mode 100644 src/server/tasks/defineTask.ts create mode 100644 src/server/tasks/queryLLM.task.ts create mode 100644 src/server/tasks/worker.ts create mode 100644 src/server/utils/generateNewCell.ts create mode 100644 src/server/utils/queueLLMRetrievalTask.ts create mode 100644 src/server/utils/shouldStream.ts create mode 100644 src/server/utils/sleep.ts diff --git a/package.json b/package.json index 34dec99..a8c1553 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build": "next build", "dev:next": "next dev", "dev:wss": "pnpm tsx --watch src/wss-server.ts", + "dev:worker": "NODE_ENV='development' pnpm tsx --watch src/server/tasks/worker.ts", "dev": "concurrently --kill-others 'pnpm dev:next' 'pnpm dev:wss'", "postinstall": "prisma generate", "lint": "next lint", @@ -44,6 +45,7 @@ "express": "^4.18.2", "framer-motion": "^10.12.17", "gpt-tokens": "^1.0.10", + "graphile-worker": "^0.13.0", "immer": "^10.0.2", "isolated-vm": "^4.5.0", "json-stringify-pretty-compact": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05fa609..2896a20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -83,6 +83,9 @@ dependencies: gpt-tokens: specifier: ^1.0.10 version: 1.0.10 + graphile-worker: + specifier: ^0.13.0 + version: 0.13.0 immer: specifier: ^10.0.2 version: 10.0.2 @@ -229,12 +232,10 @@ devDependencies: packages: - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: true /@babel/code-frame@7.22.5: resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} @@ -497,8 +498,8 @@ packages: '@babel/plugin-transform-typescript': 7.22.9(@babel/core@7.22.9) dev: false - /@babel/runtime@7.22.5: - resolution: {integrity: sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==} + /@babel/runtime@7.22.6: + resolution: {integrity: sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 @@ -762,7 +763,7 @@ packages: dependencies: '@chakra-ui/dom-utils': 2.1.0 react: 18.2.0 - react-focus-lock: 2.9.4(@types/react@18.2.6)(react@18.2.0) + react-focus-lock: 2.9.5(@types/react@18.2.6)(react@18.2.0) transitivePeerDependencies: - '@types/react' dev: false @@ -1647,7 +1648,7 @@ packages: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: '@babel/helper-module-imports': 7.22.5 - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.2 @@ -1705,7 +1706,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 '@emotion/babel-plugin': 11.11.0 '@emotion/cache': 11.11.0 '@emotion/serialize': 1.1.2 @@ -1755,7 +1756,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 '@emotion/babel-plugin': 11.11.0 '@emotion/is-prop-valid': 1.2.1 '@emotion/react': 11.11.1(@types/react@18.2.6)(react@18.2.0) @@ -1790,7 +1791,7 @@ packages: resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} dependencies: '@esbuild-kit/core-utils': 3.1.0 - get-tsconfig: 4.6.0 + get-tsconfig: 4.6.2 dev: false /@esbuild-kit/core-utils@3.1.0: @@ -1804,7 +1805,7 @@ packages: resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==} dependencies: '@esbuild-kit/core-utils': 3.1.0 - get-tsconfig: 4.6.0 + get-tsconfig: 4.6.2 dev: false /@esbuild/android-arm64@0.17.19: @@ -2218,13 +2219,13 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.0.3: - resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} + /@eslint/eslintrc@2.1.0: + resolution: {integrity: sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.5.2 + espree: 9.6.1 globals: 13.20.0 ignore: 5.2.4 import-fresh: 3.3.0 @@ -2240,6 +2241,10 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@graphile/logger@0.2.0: + resolution: {integrity: sha512-jjcWBokl9eb1gVJ85QmoaQ73CQ52xAaOCF29ukRbYNl6lY+ts0ErTaDYOBlejcbUs2OpaiqYLO5uDhyLFzWw4w==} + dev: false + /@humanwhocodes/config-array@0.11.10: resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} engines: {node: '>=10.10.0'} @@ -2437,7 +2442,7 @@ packages: resolution: {integrity: sha512-E6s9hfQx125CfGXW5896s0ZtUEecTS69KvqkNDPxKomeZ/Y2rNsG90yO8K47uchXqKw5RhD/rCNcOJ2VODfQiw==} dependencies: '@types/json-schema': 7.0.12 - '@types/node': 20.3.1 + '@types/node': 20.4.2 fast-deep-equal: 3.1.3 openapi-typescript: 5.4.1 dev: true @@ -2446,8 +2451,8 @@ packages: resolution: {integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==} dev: false - /@pkgr/utils@2.4.1: - resolution: {integrity: sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==} + /@pkgr/utils@2.4.2: + resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dependencies: cross-spawn: 7.0.3 @@ -2455,7 +2460,7 @@ packages: is-glob: 4.0.3 open: 9.1.0 picocolors: 1.0.0 - tslib: 2.5.3 + tslib: 2.6.0 dev: true /@popperjs/core@2.11.8: @@ -2499,7 +2504,7 @@ packages: /@swc/helpers@0.5.1: resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==} dependencies: - tslib: 2.5.3 + tslib: 2.6.0 dev: false /@t3-oss/env-core@0.3.1(typescript@5.0.4)(zod@3.21.4): @@ -2681,6 +2686,12 @@ packages: dependencies: '@types/node': 18.16.0 + /@types/debug@4.1.8: + resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} + dependencies: + '@types/ms': 0.7.31 + dev: false + /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: @@ -2717,10 +2728,10 @@ packages: '@types/serve-static': 1.15.2 dev: true - /@types/hast@2.3.4: - resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==} + /@types/hast@2.3.5: + resolution: {integrity: sha512-SvQi0L/lNpThgPoleH53cdjB3y9zpLlVjRbqB3rH8hx1jiRSBGAhyjV3H+URFjNVRqt2EdYNrbZE5IsGlNfpRg==} dependencies: - '@types/unist': 2.0.6 + '@types/unist': 2.0.7 dev: false /@types/http-errors@2.0.1: @@ -2752,6 +2763,10 @@ packages: resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} dev: true + /@types/ms@0.7.31: + resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} + dev: false + /@types/node-fetch@2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: @@ -2762,14 +2777,22 @@ packages: /@types/node@18.16.0: resolution: {integrity: sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==} - /@types/node@20.3.1: - resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} + /@types/node@20.4.2: + resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==} dev: true /@types/parse-json@4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: false + /@types/pg@8.10.2: + resolution: {integrity: sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw==} + dependencies: + '@types/node': 18.16.0 + pg-protocol: 1.6.0 + pg-types: 4.0.1 + dev: false + /@types/pluralize@0.0.30: resolution: {integrity: sha512-kVww6xZrW/db5BR9OqiT71J9huRdQ+z/r+LbDuT7/EK50mCmj5FoaIARnVv0rvjUS/YpDox0cDU9lpQT011VBA==} dev: true @@ -2825,8 +2848,8 @@ packages: '@types/node': 18.16.0 dev: true - /@types/unist@2.0.6: - resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} + /@types/unist@2.0.7: + resolution: {integrity: sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g==} dev: false /@typescript-eslint/eslint-plugin@5.59.6(@typescript-eslint/parser@5.59.6)(eslint@8.40.0)(typescript@5.0.4): @@ -2850,7 +2873,7 @@ packages: grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - semver: 7.5.2 + semver: 7.5.4 tsutils: 3.21.0(typescript@5.0.4) typescript: 5.0.4 transitivePeerDependencies: @@ -2924,7 +2947,7 @@ packages: debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.2 + semver: 7.5.4 tsutils: 3.21.0(typescript@5.0.4) typescript: 5.0.4 transitivePeerDependencies: @@ -2945,7 +2968,7 @@ packages: '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.0.4) eslint: 8.40.0 eslint-scope: 5.1.1 - semver: 7.5.2 + semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript @@ -3134,20 +3157,20 @@ packages: negotiator: 0.6.3 dev: false - /acorn-import-assertions@1.9.0(acorn@8.9.0): + /acorn-import-assertions@1.9.0(acorn@8.10.0): resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} peerDependencies: acorn: ^8 dependencies: - acorn: 8.9.0 + acorn: 8.10.0 dev: true - /acorn-jsx@5.3.2(acorn@8.9.0): + /acorn-jsx@5.3.2(acorn@8.10.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.9.0 + acorn: 8.10.0 dev: true /acorn-walk@8.2.0: @@ -3155,8 +3178,8 @@ packages: engines: {node: '>=0.4.0'} dev: true - /acorn@8.9.0: - resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} + /acorn@8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -3231,11 +3254,11 @@ packages: resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} engines: {node: '>=10'} dependencies: - tslib: 2.5.3 + tslib: 2.6.0 dev: false - /aria-query@5.2.1: - resolution: {integrity: sha512-7uFg4b+lETFgdaJyETnILsXgnnzVnkHcgRbwbPwevm5x/LmUlt3MjczMRe1zg824iBgXZNRPTBftNYyRSKLp2g==} + /aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: dequal: 2.0.3 dev: true @@ -3257,7 +3280,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 get-intrinsic: 1.2.1 is-string: 1.0.7 dev: true @@ -3273,7 +3296,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 es-shim-unscopables: 1.0.0 dev: true @@ -3283,7 +3306,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 es-shim-unscopables: 1.0.0 dev: true @@ -3292,7 +3315,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 es-shim-unscopables: 1.0.0 get-intrinsic: 1.2.1 dev: true @@ -3329,7 +3352,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 cosmiconfig: 7.1.0 resolve: 1.22.2 dev: false @@ -3410,8 +3433,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001506 - electron-to-chromium: 1.4.459 + caniuse-lite: 1.0.30001515 + electron-to-chromium: 1.4.461 node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.9) @@ -3422,6 +3445,11 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + /buffer-writer@2.0.0: + resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} + engines: {node: '>=4'} + dev: false + /bundle-name@3.0.0: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} @@ -3455,8 +3483,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - /caniuse-lite@1.0.30001506: - resolution: {integrity: sha512-6XNEcpygZMCKaufIcgpQNZNf00GEqc7VQON+9Rd0K1bMYo8xhMZRAo5zpbnbMNizi4YNgIDAFrdykWsvY3H4Hw==} + /caniuse-lite@1.0.30001515: + resolution: {integrity: sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA==} /chai@4.3.7: resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} @@ -3534,6 +3562,14 @@ packages: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: false + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: false + /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -3698,7 +3734,7 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} dependencies: - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 dev: false /dayjs@1.11.8: @@ -3862,8 +3898,9 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false - /electron-to-chromium@1.4.459: - resolution: {integrity: sha512-XXRS5NFv8nCrBL74Rm3qhJjA2VCsRFx0OjHKBMPI0otij56aun8UWiKTDABmd5/7GTR021pA4wivs+Ri6XCElg==} + /electron-to-chromium@1.4.461: + resolution: {integrity: sha512-1JkvV2sgEGTDXjdsaQCeSwYYuhLRphRpc+g6EHTFELJXEiznLt3/0pZ9JuAOQ5p2rI3YxKTbivtvajirIfhrEQ==} + dev: true /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3936,8 +3973,8 @@ packages: is-arrayish: 0.2.1 dev: false - /es-abstract@1.21.2: - resolution: {integrity: sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==} + /es-abstract@1.21.3: + resolution: {integrity: sha512-ZU4miiY1j3sGPFLJ34VJXEqhpmL+HGByCinGHv4HC+Fxl2fI2Z4yR6tl0mORnDr6PA8eihWo4LmSWDbvhALckg==} engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.0 @@ -3971,9 +4008,10 @@ packages: string.prototype.trim: 1.2.7 string.prototype.trimend: 1.0.6 string.prototype.trimstart: 1.0.6 + typed-array-byte-offset: 1.0.0 typed-array-length: 1.0.4 unbox-primitive: 1.0.2 - which-typed-array: 1.1.9 + which-typed-array: 1.1.10 dev: true /es-module-lexer@1.3.0: @@ -4127,8 +4165,8 @@ packages: eslint: 8.40.0 eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) - get-tsconfig: 4.6.0 - globby: 13.2.0 + get-tsconfig: 4.6.2 + globby: 13.2.2 is-core-module: 2.12.1 is-glob: 4.0.3 synckit: 0.8.5 @@ -4208,8 +4246,8 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 dependencies: - '@babel/runtime': 7.22.5 - aria-query: 5.2.1 + '@babel/runtime': 7.22.6 + aria-query: 5.3.0 array-includes: 3.1.6 array.prototype.flatmap: 1.3.1 ast-types-flow: 0.0.7 @@ -4219,7 +4257,7 @@ packages: emoji-regex: 9.2.2 eslint: 8.40.0 has: 1.0.3 - jsx-ast-utils: 3.3.3 + jsx-ast-utils: 3.3.4 language-tags: 1.0.5 minimatch: 3.1.2 object.entries: 1.1.6 @@ -4248,7 +4286,7 @@ packages: doctrine: 2.1.0 eslint: 8.40.0 estraverse: 5.3.0 - jsx-ast-utils: 3.3.3 + jsx-ast-utils: 3.3.4 minimatch: 3.1.2 object.entries: 1.1.6 object.fromentries: 2.0.6 @@ -4288,8 +4326,8 @@ packages: estraverse: 4.3.0 dev: true - /eslint-scope@7.2.0: - resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} + /eslint-scope@7.2.1: + resolution: {integrity: sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 @@ -4308,7 +4346,7 @@ packages: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) '@eslint-community/regexpp': 4.5.1 - '@eslint/eslintrc': 2.0.3 + '@eslint/eslintrc': 2.1.0 '@eslint/js': 8.40.0 '@humanwhocodes/config-array': 0.11.10 '@humanwhocodes/module-importer': 1.0.1 @@ -4319,9 +4357,9 @@ packages: debug: 4.3.4 doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.0 + eslint-scope: 7.2.1 eslint-visitor-keys: 3.4.1 - espree: 9.5.2 + espree: 9.6.1 esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -4342,7 +4380,7 @@ packages: lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 - optionator: 0.9.1 + optionator: 0.9.3 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 text-table: 0.2.0 @@ -4350,12 +4388,12 @@ packages: - supports-color dev: true - /espree@9.5.2: - resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.9.0 - acorn-jsx: 5.3.2(acorn@8.9.0) + acorn: 8.10.0 + acorn-jsx: 5.3.2(acorn@8.10.0) eslint-visitor-keys: 3.4.1 dev: true @@ -4567,7 +4605,7 @@ packages: resolution: {integrity: sha512-KSuV3ur4gf2KqMNoZx3nXNVhqCkn42GuTYCX4tXPEwf0MjpFQmNMiN6m7dXaUXgIoivL6/65agoUMg4RLS0Vbg==} engines: {node: '>=10'} dependencies: - tslib: 2.5.3 + tslib: 2.6.0 dev: false /for-each@0.3.3: @@ -4620,7 +4658,7 @@ packages: dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - tslib: 2.5.3 + tslib: 2.6.0 optionalDependencies: '@emotion/is-prop-valid': 0.8.8 dev: false @@ -4656,7 +4694,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 functions-have-names: 1.2.3 dev: true @@ -4703,8 +4741,8 @@ packages: get-intrinsic: 1.2.1 dev: true - /get-tsconfig@4.6.0: - resolution: {integrity: sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==} + /get-tsconfig@4.6.2: + resolution: {integrity: sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==} dependencies: resolve-pkg-maps: 1.0.0 @@ -4781,8 +4819,8 @@ packages: slash: 3.0.0 dev: true - /globby@13.2.0: - resolution: {integrity: sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==} + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: dir-glob: 3.0.1 @@ -4817,6 +4855,24 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /graphile-worker@0.13.0: + resolution: {integrity: sha512-8Hl5XV6hkabZRhYzvbUfvjJfPFR5EPxYRVWlzQC2rqYHrjULTLBgBYZna5R9ukbnsbWSvn4vVrzOBIOgIC1jjw==} + engines: {node: '>=10.0.0'} + hasBin: true + dependencies: + '@graphile/logger': 0.2.0 + '@types/debug': 4.1.8 + '@types/pg': 8.10.2 + chokidar: 3.5.3 + cosmiconfig: 7.1.0 + json5: 2.2.3 + pg: 8.11.1 + tslib: 2.6.0 + yargs: 16.2.0 + transitivePeerDependencies: + - pg-native + dev: false + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -4863,7 +4919,7 @@ packages: /hastscript@6.0.0: resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} dependencies: - '@types/hast': 2.3.4 + '@types/hast': 2.3.5 comma-separated-tokens: 1.0.8 hast-util-parse-selector: 2.2.5 property-information: 5.6.0 @@ -5259,12 +5315,14 @@ packages: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true - /jsx-ast-utils@3.3.3: - resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} + /jsx-ast-utils@3.3.4: + resolution: {integrity: sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==} engines: {node: '>=4.0'} dependencies: array-includes: 3.1.6 + array.prototype.flat: 1.3.1 object.assign: 4.1.4 + object.values: 1.1.6 dev: true /language-subtag-registry@0.3.22: @@ -5447,7 +5505,7 @@ packages: /mlly@1.4.0: resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} dependencies: - acorn: 8.9.0 + acorn: 8.10.0 pathe: 1.1.1 pkg-types: 1.0.3 ufo: 1.1.2 @@ -5506,15 +5564,15 @@ packages: nodemailer: optional: true dependencies: - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 '@panva/hkdf': 1.1.1 cookie: 0.5.0 jose: 4.14.4 next: 13.4.2(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) oauth: 0.9.15 - openid-client: 5.4.2 - preact: 10.15.1 - preact-render-to-string: 5.2.6(preact@10.15.1) + openid-client: 5.4.3 + preact: 10.16.0 + preact-render-to-string: 5.2.6(preact@10.16.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) uuid: 8.3.2 @@ -5544,7 +5602,7 @@ packages: '@next/env': 13.4.2 '@swc/helpers': 0.5.1 busboy: 1.6.0 - caniuse-lite: 1.0.30001506 + caniuse-lite: 1.0.30001515 postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -5655,7 +5713,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 dev: true /object.fromentries@2.0.6: @@ -5664,14 +5722,14 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 dev: true /object.hasown@1.1.2: resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} dependencies: define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 dev: true /object.values@1.1.6: @@ -5680,9 +5738,13 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 dev: true + /obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + dev: false + /oidc-token-hash@5.0.3: resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} engines: {node: ^10.13.0 || >=12.0.0} @@ -5737,7 +5799,7 @@ packages: form-data-encoder: 1.7.2 formdata-node: 4.4.1 node-fetch: 2.6.12 - qs: 6.11.0 + qs: 6.11.2 transitivePeerDependencies: - encoding - supports-color @@ -5768,8 +5830,8 @@ packages: yargs-parser: 21.1.1 dev: true - /openid-client@5.4.2: - resolution: {integrity: sha512-lIhsdPvJ2RneBm3nGBBhQchpe3Uka//xf7WPHTIglery8gnckvW7Bd9IaQzekzXJvWthCMyi/xVEyGW0RFPytw==} + /openid-client@5.4.3: + resolution: {integrity: sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==} dependencies: jose: 4.14.4 lru-cache: 6.0.0 @@ -5777,16 +5839,16 @@ packages: oidc-token-hash: 5.0.3 dev: false - /optionator@0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - word-wrap: 1.2.3 dev: true /p-limit@3.1.0: @@ -5810,6 +5872,10 @@ packages: p-limit: 3.1.0 dev: true + /packet-reader@1.0.0: + resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} + dev: false + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -5881,6 +5947,88 @@ packages: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true + /pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + requiresBuild: true + dev: false + optional: true + + /pg-connection-string@2.6.1: + resolution: {integrity: sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==} + dev: false + + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + dev: false + + /pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + dev: false + + /pg-pool@3.6.1(pg@8.11.1): + resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.11.1 + dev: false + + /pg-protocol@1.6.0: + resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} + dev: false + + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + dev: false + + /pg-types@4.0.1: + resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==} + engines: {node: '>=10'} + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.0.1 + postgres-interval: 3.0.0 + postgres-range: 1.1.3 + dev: false + + /pg@8.11.1: + resolution: {integrity: sha512-utdq2obft07MxaDg0zBJI+l/M3mBRfIpEN3iSemsz0G5F2/VXx+XzqF4oxrbIZXQxt2AZzIUzyVg/YM6xOP/WQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-connection-string: 2.6.1 + pg-pool: 3.6.1(pg@8.11.1) + pg-protocol: 1.6.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + dev: false + + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + dependencies: + split2: 4.2.0 + dev: false + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -5919,23 +6067,71 @@ packages: source-map-js: 1.0.2 dev: true + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + dev: false + + /postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + dev: false + + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + dependencies: + obuf: 1.1.2 + dev: false + + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-date@2.0.1: + resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==} + engines: {node: '>=12'} + dev: false + + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + dev: false + + /postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + dev: false + + /postgres-range@1.1.3: + resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==} + dev: false + /posthog-js@1.68.4: resolution: {integrity: sha512-rHk4uk99nvWiDTU7P2mFdEfzFR6km0hvOpCR3tm/+F7kCJKs7QDkMblOZZHZultxM4wSNyB4neeohmnHjKYUhQ==} dependencies: fflate: 0.4.8 dev: false - /preact-render-to-string@5.2.6(preact@10.15.1): + /preact-render-to-string@5.2.6(preact@10.16.0): resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==} peerDependencies: preact: '>=10' dependencies: - preact: 10.15.1 + preact: 10.16.0 pretty-format: 3.8.0 dev: false - /preact@10.15.1: - resolution: {integrity: sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==} + /preact@10.16.0: + resolution: {integrity: sha512-XTSj3dJ4roKIC93pald6rWuB2qQJO9gO2iLLyTe87MrjQN+HklueLsmskbywEWqCHlclgz3/M4YLL2iBr9UmMA==} dev: false /prelude-ls@1.2.1: @@ -6023,6 +6219,13 @@ packages: side-channel: 1.0.4 dev: false + /qs@6.11.2: + resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -6064,7 +6267,7 @@ packages: peerDependencies: react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 react: 18.2.0 dev: false @@ -6082,8 +6285,8 @@ packages: resolution: {integrity: sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg==} dev: false - /react-focus-lock@2.9.4(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-7pEdXyMseqm3kVjhdVH18sovparAzLg5h6WvIx7/Ck3ekjhrrDMEegHSa3swwC8wgfdd7DIdUVRGeiHT9/7Sgg==} + /react-focus-lock@2.9.5(@types/react@18.2.6)(react@18.2.0): + resolution: {integrity: sha512-h6vrdgUbsH2HeD5I7I3Cx1PPrmwGuKYICS+kB9m+32X/9xHRrAbxgvaBpG7BFBN9h3tO+C3qX1QAVESmi4CiIA==} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -6091,7 +6294,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 '@types/react': 18.2.6 focus-lock: 0.11.6 prop-types: 15.8.1 @@ -6129,7 +6332,7 @@ packages: '@types/react': 18.2.6 react: 18.2.0 react-style-singleton: 2.2.1(@types/react@18.2.6)(react@18.2.0) - tslib: 2.5.3 + tslib: 2.6.0 dev: false /react-remove-scroll@2.5.6(@types/react@18.2.6)(react@18.2.0): @@ -6146,7 +6349,7 @@ packages: react: 18.2.0 react-remove-scroll-bar: 2.3.4(@types/react@18.2.6)(react@18.2.0) react-style-singleton: 2.2.1(@types/react@18.2.6)(react@18.2.0) - tslib: 2.5.3 + tslib: 2.6.0 use-callback-ref: 1.3.0(@types/react@18.2.6)(react@18.2.0) use-sidecar: 1.1.2(@types/react@18.2.6)(react@18.2.0) dev: false @@ -6173,7 +6376,7 @@ packages: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.2.0 - tslib: 2.5.3 + tslib: 2.6.0 dev: false /react-syntax-highlighter@15.5.0(react@18.2.0): @@ -6181,7 +6384,7 @@ packages: peerDependencies: react: '>= 0.14.0' dependencies: - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 highlight.js: 10.7.3 lowlight: 1.20.0 prismjs: 1.29.0 @@ -6195,7 +6398,7 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.22.5 + '@babel/runtime': 7.22.6 react: 18.2.0 use-composed-ref: 1.3.0(react@18.2.0) use-latest: 1.2.1(@types/react@18.2.6)(react@18.2.0) @@ -6323,7 +6526,7 @@ packages: /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 2.5.3 + tslib: 2.6.0 dev: false /safe-buffer@5.1.2: @@ -6364,8 +6567,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - /semver@7.5.2: - resolution: {integrity: sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==} + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} engines: {node: '>=10'} hasBin: true dependencies: @@ -6533,6 +6736,11 @@ packages: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} dev: false + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + /stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} dev: true @@ -6568,7 +6776,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 get-intrinsic: 1.2.1 has-symbols: 1.0.3 internal-slot: 1.0.5 @@ -6582,7 +6790,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 dev: true /string.prototype.trimend@1.0.6: @@ -6590,7 +6798,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 dev: true /string.prototype.trimstart@1.0.6: @@ -6598,7 +6806,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.2 + es-abstract: 1.21.3 dev: true /string_decoder@0.10.31: @@ -6640,7 +6848,7 @@ packages: /strip-literal@1.0.1: resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} dependencies: - acorn: 8.9.0 + acorn: 8.10.0 dev: true /styled-jsx@5.1.1(@babel/core@7.22.9)(react@18.2.0): @@ -6703,8 +6911,8 @@ packages: resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} engines: {node: ^14.18.0 || >=16.0.0} dependencies: - '@pkgr/utils': 2.4.1 - tslib: 2.5.3 + '@pkgr/utils': 2.4.2 + tslib: 2.6.0 dev: true /tapable@2.2.1: @@ -6742,7 +6950,7 @@ packages: hasBin: true dependencies: '@jridgewell/source-map': 0.3.5 - acorn: 8.9.0 + acorn: 8.10.0 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -6837,8 +7045,8 @@ packages: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} dev: false - /tslib@2.5.3: - resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} + /tslib@2.6.0: + resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} /tsutils@3.21.0(typescript@5.0.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -6886,6 +7094,17 @@ packages: mime-types: 2.1.35 dev: false + /typed-array-byte-offset@1.0.0: + resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.10 + dev: true + /typed-array-length@1.0.4: resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} dependencies: @@ -6957,7 +7176,7 @@ packages: dependencies: '@types/react': 18.2.6 react: 18.2.0 - tslib: 2.5.3 + tslib: 2.6.0 dev: false /use-composed-ref@1.3.0(react@18.2.0): @@ -7008,7 +7227,7 @@ packages: '@types/react': 18.2.6 detect-node-es: 1.1.0 react: 18.2.0 - tslib: 2.5.3 + tslib: 2.6.0 dev: false /use-sync-external-store@1.2.0(react@18.2.0): @@ -7048,7 +7267,7 @@ packages: mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.4.3(@types/node@18.16.0) + vite: 4.4.4(@types/node@18.16.0) transitivePeerDependencies: - '@types/node' - less @@ -7060,8 +7279,8 @@ packages: - terser dev: true - /vite@4.4.3(@types/node@18.16.0): - resolution: {integrity: sha512-IMnXQXXWgLi5brBQx/4WzDxdzW0X3pjO4nqFJAuNvwKtxzAmPzFE1wszW3VDpAGQJm3RZkm/brzRdyGsnwgJIA==} + /vite@4.4.4(@types/node@18.16.0): + resolution: {integrity: sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -7135,7 +7354,7 @@ packages: '@vitest/snapshot': 0.33.0 '@vitest/spy': 0.33.0 '@vitest/utils': 0.33.0 - acorn: 8.9.0 + acorn: 8.10.0 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 @@ -7148,7 +7367,7 @@ packages: strip-literal: 1.0.1 tinybench: 2.5.0 tinypool: 0.6.0 - vite: 4.4.3(@types/node@18.16.0) + vite: 4.4.4(@types/node@18.16.0) vite-node: 0.33.0(@types/node@18.16.0) why-is-node-running: 2.2.2 transitivePeerDependencies: @@ -7198,8 +7417,8 @@ packages: '@webassemblyjs/ast': 1.11.6 '@webassemblyjs/wasm-edit': 1.11.6 '@webassemblyjs/wasm-parser': 1.11.6 - acorn: 8.9.0 - acorn-import-assertions: 1.9.0(acorn@8.9.0) + acorn: 8.10.0 + acorn-import-assertions: 1.9.0(acorn@8.10.0) browserslist: 4.21.9 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 @@ -7240,8 +7459,8 @@ packages: is-symbol: 1.0.4 dev: true - /which-typed-array@1.1.9: - resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} + /which-typed-array@1.1.10: + resolution: {integrity: sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==} engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.5 @@ -7269,11 +7488,6 @@ packages: stackback: 0.0.2 dev: true - /word-wrap@1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} - dev: true - /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -7338,10 +7552,28 @@ packages: engines: {node: '>= 14'} dev: true + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: false + /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + dev: false + /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} diff --git a/prisma/migrations/20230714113205_create_scenariotvariant/migration.sql b/prisma/migrations/20230714113205_create_scenariotvariant/migration.sql new file mode 100644 index 0000000..dbe1cba --- /dev/null +++ b/prisma/migrations/20230714113205_create_scenariotvariant/migration.sql @@ -0,0 +1,49 @@ +-- Drop the foreign key constraints on the original ModelOutput +ALTER TABLE "ModelOutput" DROP CONSTRAINT "ModelOutput_promptVariantId_fkey"; +ALTER TABLE "ModelOutput" DROP CONSTRAINT "ModelOutput_testScenarioId_fkey"; + +-- Rename the old table +ALTER TABLE "ModelOutput" RENAME TO "ScenarioVariantCell"; +ALTER TABLE "ScenarioVariantCell" RENAME CONSTRAINT "ModelOutput_pkey" TO "ScenarioVariantCell_pkey"; +ALTER INDEX "ModelOutput_inputHash_idx" RENAME TO "ScenarioVariantCell_inputHash_idx"; +ALTER INDEX "ModelOutput_promptVariantId_testScenarioId_key" RENAME TO "ScenarioVariantCell_promptVariantId_testScenarioId_key"; + +-- Add the new fields to the renamed table +ALTER TABLE "ScenarioVariantCell" ADD COLUMN "retryTime" TIMESTAMP(3); +ALTER TABLE "ScenarioVariantCell" ADD COLUMN "streamingChannel" TEXT; +ALTER TABLE "ScenarioVariantCell" ALTER COLUMN "inputHash" DROP NOT NULL; +ALTER TABLE "ScenarioVariantCell" ALTER COLUMN "output" DROP NOT NULL, +ALTER COLUMN "statusCode" DROP NOT NULL, +ALTER COLUMN "timeToComplete" DROP NOT NULL; + +-- Create the new table +CREATE TABLE "ModelOutput" ( + "id" UUID NOT NULL, + "inputHash" TEXT NOT NULL, + "output" JSONB NOT NULL, + "timeToComplete" INTEGER NOT NULL DEFAULT 0, + "promptTokens" INTEGER, + "completionTokens" INTEGER, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "scenarioVariantCellId" UUID +); + +-- Move inputHash index +DROP INDEX "ScenarioVariantCell_inputHash_idx"; +CREATE INDEX "ModelOutput_inputHash_idx" ON "ModelOutput"("inputHash"); + +CREATE UNIQUE INDEX "ModelOutput_scenarioVariantCellId_key" ON "ModelOutput"("scenarioVariantCellId"); +ALTER TABLE "ModelOutput" ADD CONSTRAINT "ModelOutput_scenarioVariantCellId_fkey" FOREIGN KEY ("scenarioVariantCellId") REFERENCES "ScenarioVariantCell"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE "ModelOutput" ALTER COLUMN "scenarioVariantCellId" SET NOT NULL, +ADD CONSTRAINT "ModelOutput_pkey" PRIMARY KEY ("id"); + +ALTER TABLE "ScenarioVariantCell" ADD CONSTRAINT "ScenarioVariantCell_promptVariantId_fkey" FOREIGN KEY ("promptVariantId") REFERENCES "PromptVariant"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "ScenarioVariantCell" ADD CONSTRAINT "ScenarioVariantCell_testScenarioId_fkey" FOREIGN KEY ("testScenarioId") REFERENCES "TestScenario"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- CreateEnum +CREATE TYPE "CellRetrievalStatus" AS ENUM ('PENDING', 'IN_PROGRESS', 'COMPLETE', 'ERROR'); + +-- AlterTable +ALTER TABLE "ScenarioVariantCell" ADD COLUMN "retrievalStatus" "CellRetrievalStatus" NOT NULL DEFAULT 'COMPLETE'; diff --git a/prisma/migrations/20230714182259_add_model_to_variant/migration.sql b/prisma/migrations/20230714182259_add_model_to_variant/migration.sql new file mode 100644 index 0000000..b445e09 --- /dev/null +++ b/prisma/migrations/20230714182259_add_model_to_variant/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "PromptVariant" ADD COLUMN "model" TEXT NOT NULL DEFAULT 'gpt-3.5-turbo'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index baa9f4f..6e0d22c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -41,7 +41,7 @@ model PromptVariant { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - ModelOutput ModelOutput[] + scenarioVariantCells ScenarioVariantCell[] EvaluationResult EvaluationResult[] @@index([uiId]) @@ -61,7 +61,7 @@ model TestScenario { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - ModelOutput ModelOutput[] + scenarioVariantCells ScenarioVariantCell[] } model TemplateVariable { @@ -76,17 +76,28 @@ model TemplateVariable { updatedAt DateTime @updatedAt } -model ModelOutput { +enum CellRetrievalStatus { + PENDING + IN_PROGRESS + COMPLETE + ERROR +} + +model ScenarioVariantCell { id String @id @default(uuid()) @db.Uuid - inputHash String - output Json - statusCode Int - errorMessage String? - timeToComplete Int @default(0) + inputHash String? // TODO: Remove once migration is complete + output Json? // TODO: Remove once migration is complete + statusCode Int? + errorMessage String? + timeToComplete Int? @default(0) // TODO: Remove once migration is complete + retryTime DateTime? + streamingChannel String? + retrievalStatus CellRetrievalStatus @default(COMPLETE) - promptTokens Int? // Added promptTokens field - completionTokens Int? // Added completionTokens field + promptTokens Int? // TODO: Remove once migration is complete + completionTokens Int? // TODO: Remove once migration is complete + modelOutput ModelOutput? promptVariantId String @db.Uuid promptVariant PromptVariant @relation(fields: [promptVariantId], references: [id], onDelete: Cascade) @@ -98,6 +109,24 @@ model ModelOutput { updatedAt DateTime @updatedAt @@unique([promptVariantId, testScenarioId]) +} + +model ModelOutput { + id String @id @default(uuid()) @db.Uuid + + inputHash String + output Json + timeToComplete Int @default(0) + promptTokens Int? + completionTokens Int? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + scenarioVariantCellId String @db.Uuid + scenarioVariantCell ScenarioVariantCell @relation(fields: [scenarioVariantCellId], references: [id], onDelete: Cascade) + + @@unique([scenarioVariantCellId]) @@index([inputHash]) } diff --git a/prisma/seed.ts b/prisma/seed.ts index c7676e9..d3d4986 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -16,7 +16,7 @@ const experiment = await prisma.experiment.create({ }, }); -await prisma.modelOutput.deleteMany({ +await prisma.scenarioVariantCell.deleteMany({ where: { promptVariant: { experimentId, diff --git a/src/components/OutputsTable/OutputCell/CellOptions.tsx b/src/components/OutputsTable/OutputCell/CellOptions.tsx new file mode 100644 index 0000000..4572964 --- /dev/null +++ b/src/components/OutputsTable/OutputCell/CellOptions.tsx @@ -0,0 +1,33 @@ +import { Button, HStack, Icon } from "@chakra-ui/react"; +import { BsArrowClockwise } from "react-icons/bs"; + +export const CellOptions = ({ + refetchingOutput, + refetchOutput, +}: { + refetchingOutput: boolean; + refetchOutput: () => void; +}) => { + return ( + + {!refetchingOutput && ( + + )} + + ); +}; diff --git a/src/components/OutputsTable/OutputCell/ErrorHandler.tsx b/src/components/OutputsTable/OutputCell/ErrorHandler.tsx index 419a3e8..b626bcc 100644 --- a/src/components/OutputsTable/OutputCell/ErrorHandler.tsx +++ b/src/components/OutputsTable/OutputCell/ErrorHandler.tsx @@ -1,29 +1,21 @@ -import { type ModelOutput } from "@prisma/client"; -import { HStack, VStack, Text, Button, Icon } from "@chakra-ui/react"; +import { type ScenarioVariantCell } from "@prisma/client"; +import { VStack, Text } from "@chakra-ui/react"; import { useEffect, useState } from "react"; -import { BsArrowClockwise } from "react-icons/bs"; -import { rateLimitErrorMessage } from "~/sharedStrings"; import pluralize from "pluralize"; -const MAX_AUTO_RETRIES = 3; - export const ErrorHandler = ({ - output, + cell, refetchOutput, - numPreviousTries, }: { - output: ModelOutput; + cell: ScenarioVariantCell; refetchOutput: () => void; - numPreviousTries: number; }) => { const [msToWait, setMsToWait] = useState(0); - const shouldAutoRetry = - output.errorMessage === rateLimitErrorMessage && numPreviousTries < MAX_AUTO_RETRIES; useEffect(() => { - if (!shouldAutoRetry) return; + if (!cell.retryTime) return; - const initialWaitTime = calculateDelay(numPreviousTries); + const initialWaitTime = cell.retryTime.getTime() - Date.now(); const msModuloOneSecond = initialWaitTime % 1000; let remainingTime = initialWaitTime - msModuloOneSecond; setMsToWait(remainingTime); @@ -35,7 +27,6 @@ export const ErrorHandler = ({ setMsToWait(remainingTime); if (remainingTime <= 0) { - refetchOutput(); clearInterval(interval); } }, 1000); @@ -45,32 +36,12 @@ export const ErrorHandler = ({ clearInterval(interval); clearTimeout(timeout); }; - }, [shouldAutoRetry, setMsToWait, refetchOutput, numPreviousTries]); + }, [cell.retryTime, cell.statusCode, setMsToWait, refetchOutput]); return ( - - - Error - - - - {output.errorMessage} + {cell.errorMessage} {msToWait > 0 && ( @@ -80,12 +51,3 @@ export const ErrorHandler = ({ ); }; - -const MIN_DELAY = 500; // milliseconds -const MAX_DELAY = 5000; // milliseconds - -function calculateDelay(numPreviousTries: number): number { - const baseDelay = Math.min(MAX_DELAY, MIN_DELAY * Math.pow(2, numPreviousTries)); - const jitter = Math.random() * baseDelay; - return baseDelay + jitter; -} diff --git a/src/components/OutputsTable/OutputCell/OutputCell.tsx b/src/components/OutputsTable/OutputCell/OutputCell.tsx index 8307852..2e1585a 100644 --- a/src/components/OutputsTable/OutputCell/OutputCell.tsx +++ b/src/components/OutputsTable/OutputCell/OutputCell.tsx @@ -1,17 +1,16 @@ -import { type RouterOutputs, api } from "~/utils/api"; +import { api } from "~/utils/api"; import { type PromptVariant, type Scenario } from "../types"; -import { Spinner, Text, Box, Center, Flex } from "@chakra-ui/react"; +import { Spinner, Text, Box, Center, Flex, VStack } from "@chakra-ui/react"; import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks"; import SyntaxHighlighter from "react-syntax-highlighter"; import { docco } from "react-syntax-highlighter/dist/cjs/styles/hljs"; import stringify from "json-stringify-pretty-compact"; -import { type ReactElement, useState, useEffect, useRef, useCallback } from "react"; +import { type ReactElement, useState, useEffect } from "react"; import { type ChatCompletion } from "openai/resources/chat"; -import { generateChannel } from "~/utils/generateChannel"; -import { isObject } from "lodash"; import useSocket from "~/utils/useSocket"; import { OutputStats } from "./OutputStats"; import { ErrorHandler } from "./ErrorHandler"; +import { CellOptions } from "./CellOptions"; export default function OutputCell({ scenario, @@ -37,116 +36,103 @@ export default function OutputCell({ // if (variant.config === null || Object.keys(variant.config).length === 0) // disabledReason = "Save your prompt variant to see output"; - const outputMutation = api.outputs.get.useMutation(); - - const [output, setOutput] = useState(null); - const [channel, setChannel] = useState(undefined); - const [numPreviousTries, setNumPreviousTries] = useState(0); - - const fetchMutex = useRef(false); - const [fetchOutput, fetchingOutput] = useHandledAsyncCallback( - async (forceRefetch?: boolean) => { - if (fetchMutex.current) return; - setNumPreviousTries((prev) => prev + 1); - - fetchMutex.current = true; - setOutput(null); - - const shouldStream = - isObject(variant) && - "config" in variant && - isObject(variant.config) && - "stream" in variant.config && - variant.config.stream === true; - - const channel = shouldStream ? generateChannel() : undefined; - setChannel(channel); - - const output = await outputMutation.mutateAsync({ - scenarioId: scenario.id, - variantId: variant.id, - channel, - forceRefetch, - }); - setOutput(output); - await utils.promptVariants.stats.invalidate(); - fetchMutex.current = false; - }, - [outputMutation, scenario.id, variant.id], + const [refetchInterval, setRefetchInterval] = useState(0); + const { data: cell, isLoading: queryLoading } = api.scenarioVariantCells.get.useQuery( + { scenarioId: scenario.id, variantId: variant.id }, + { refetchInterval }, ); - const hardRefetch = useCallback(() => fetchOutput(true), [fetchOutput]); - useEffect(fetchOutput, [scenario.id, variant.id]); + const { mutateAsync: hardRefetchMutate, isLoading: refetchingOutput } = + api.scenarioVariantCells.forceRefetch.useMutation(); + const [hardRefetch] = useHandledAsyncCallback(async () => { + await hardRefetchMutate({ scenarioId: scenario.id, variantId: variant.id }); + await utils.scenarioVariantCells.get.invalidate({ + scenarioId: scenario.id, + variantId: variant.id, + }); + }, [hardRefetchMutate, scenario.id, variant.id]); + + const fetchingOutput = queryLoading || refetchingOutput; + + const awaitingOutput = + !cell || cell.retrievalStatus === "PENDING" || cell.retrievalStatus === "IN_PROGRESS"; + useEffect(() => setRefetchInterval(awaitingOutput ? 1000 : 0), [awaitingOutput]); + + const modelOutput = cell?.modelOutput; // Disconnect from socket if we're not streaming anymore - const streamedMessage = useSocket(fetchingOutput ? channel : undefined); + const streamedMessage = useSocket(cell?.streamingChannel); const streamedContent = streamedMessage?.choices?.[0]?.message?.content; if (!vars) return null; if (disabledReason) return {disabledReason}; - if (fetchingOutput && !streamedMessage) + if (awaitingOutput && !streamedMessage) return (
); - if (!output && !fetchingOutput) return Error retrieving output; + if (!cell && !fetchingOutput) return Error retrieving output; - if (output && output.errorMessage) { - return ( - - ); + if (cell && cell.errorMessage) { + return ; } - const response = output?.output as unknown as ChatCompletion; + const response = modelOutput?.output as unknown as ChatCompletion; const message = response?.choices?.[0]?.message; - if (output && message?.function_call) { + if (modelOutput && message?.function_call) { const rawArgs = message.function_call.arguments ?? "null"; let parsedArgs: string; try { parsedArgs = JSON.parse(rawArgs); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { parsedArgs = `Failed to parse arguments as JSON: '${rawArgs}' ERROR: ${e.message as string}`; } return ( - - {stringify( - { - function: message.function_call.name, - args: parsedArgs, - }, - { maxLength: 40 }, - )} - - + + + + {stringify( + { + function: message.function_call.name, + args: parsedArgs, + }, + { maxLength: 40 }, + )} + + + ); } - const contentToDisplay = message?.content ?? streamedContent ?? JSON.stringify(output?.output); + const contentToDisplay = + message?.content ?? streamedContent ?? JSON.stringify(modelOutput?.output); return ( - {contentToDisplay} - {output && } + + + {contentToDisplay} + + {modelOutput && ( + + )} ); } diff --git a/src/components/OutputsTable/OutputCell/OutputStats.tsx b/src/components/OutputsTable/OutputCell/OutputStats.tsx index 02e5780..580f4f9 100644 --- a/src/components/OutputsTable/OutputCell/OutputStats.tsx +++ b/src/components/OutputsTable/OutputCell/OutputStats.tsx @@ -9,8 +9,8 @@ import { HStack, Icon, Text } from "@chakra-ui/react"; import { BsCheck, BsClock, BsCurrencyDollar, BsX } from "react-icons/bs"; import { CostTooltip } from "~/components/tooltip/CostTooltip"; -const SHOW_COST = false; -const SHOW_TIME = false; +const SHOW_COST = true; +const SHOW_TIME = true; export const OutputStats = ({ model, @@ -35,8 +35,6 @@ export const OutputStats = ({ const cost = promptCost + completionCost; - if (!evals.length) return null; - return ( diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a20c3a6..ac4897c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,7 +1,7 @@ import { type GetServerSideProps } from "next"; // eslint-disable-next-line @typescript-eslint/require-await -export const getServerSideProps: GetServerSideProps = async (context) => { +export const getServerSideProps: GetServerSideProps = async () => { return { redirect: { destination: "/experiments", diff --git a/src/server/api/autogen.ts b/src/server/api/autogen.ts index 516599a..1d103b4 100644 --- a/src/server/api/autogen.ts +++ b/src/server/api/autogen.ts @@ -3,10 +3,6 @@ import { prisma } from "../db"; import { openai } from "../utils/openai"; import { pick } from "lodash"; -function promptHasVariable(prompt: string, variableName: string) { - return prompt.includes(`{{${variableName}}}`); -} - type AxiosError = { response?: { data?: { diff --git a/src/server/api/root.router.ts b/src/server/api/root.router.ts index cb1ff9a..271879e 100644 --- a/src/server/api/root.router.ts +++ b/src/server/api/root.router.ts @@ -2,7 +2,7 @@ import { promptVariantsRouter } from "~/server/api/routers/promptVariants.router import { createTRPCRouter } from "~/server/api/trpc"; import { experimentsRouter } from "./routers/experiments.router"; import { scenariosRouter } from "./routers/scenarios.router"; -import { modelOutputsRouter } from "./routers/modelOutputs.router"; +import { scenarioVariantCellsRouter } from "./routers/scenarioVariantCells.router"; import { templateVarsRouter } from "./routers/templateVariables.router"; import { evaluationsRouter } from "./routers/evaluations.router"; @@ -15,7 +15,7 @@ export const appRouter = createTRPCRouter({ promptVariants: promptVariantsRouter, experiments: experimentsRouter, scenarios: scenariosRouter, - outputs: modelOutputsRouter, + scenarioVariantCells: scenarioVariantCellsRouter, templateVars: templateVarsRouter, evaluations: evaluationsRouter, }); diff --git a/src/server/api/routers/experiments.router.ts b/src/server/api/routers/experiments.router.ts index 1c47d7a..7f5aab9 100644 --- a/src/server/api/routers/experiments.router.ts +++ b/src/server/api/routers/experiments.router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; import { prisma } from "~/server/db"; import dedent from "dedent"; +import { generateNewCell } from "~/server/utils/generateNewCell"; export const experimentsRouter = createTRPCRouter({ list: publicProcedure.query(async () => { @@ -64,7 +65,7 @@ export const experimentsRouter = createTRPCRouter({ }, }); - await prisma.$transaction([ + const [variant, scenario] = await prisma.$transaction([ prisma.promptVariant.create({ data: { experimentId: exp.id, @@ -86,6 +87,8 @@ export const experimentsRouter = createTRPCRouter({ }), ]); + await generateNewCell(variant.id, scenario.id); + return exp; }), diff --git a/src/server/api/routers/modelOutputs.router.ts b/src/server/api/routers/modelOutputs.router.ts deleted file mode 100644 index 7b11850..0000000 --- a/src/server/api/routers/modelOutputs.router.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { z } from "zod"; -import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; -import { prisma } from "~/server/db"; -import crypto from "crypto"; -import type { Prisma } from "@prisma/client"; -import { reevaluateVariant } from "~/server/utils/evaluations"; -import { getCompletion } from "~/server/utils/getCompletion"; -import { constructPrompt } from "~/server/utils/constructPrompt"; -import { type CompletionCreateParams } from "openai/resources/chat"; - -export const modelOutputsRouter = createTRPCRouter({ - get: publicProcedure - .input( - z.object({ - scenarioId: z.string(), - variantId: z.string(), - channel: z.string().optional(), - forceRefetch: z.boolean().optional(), - }), - ) - .mutation(async ({ input }) => { - const existing = await prisma.modelOutput.findUnique({ - where: { - promptVariantId_testScenarioId: { - promptVariantId: input.variantId, - testScenarioId: input.scenarioId, - }, - }, - }); - - if (existing && !input.forceRefetch) return existing; - - const variant = await prisma.promptVariant.findUnique({ - where: { - id: input.variantId, - }, - }); - - const scenario = await prisma.testScenario.findUnique({ - where: { - id: input.scenarioId, - }, - }); - - if (!variant || !scenario) return null; - - const prompt = await constructPrompt(variant, scenario.variableValues); - - const inputHash = crypto.createHash("sha256").update(JSON.stringify(prompt)).digest("hex"); - - // TODO: we should probably only use this if temperature=0 - const existingResponse = await prisma.modelOutput.findFirst({ - where: { inputHash, errorMessage: null }, - }); - - let modelResponse: Awaited>; - - if (existingResponse) { - modelResponse = { - output: existingResponse.output as Prisma.InputJsonValue, - statusCode: existingResponse.statusCode, - errorMessage: existingResponse.errorMessage, - timeToComplete: existingResponse.timeToComplete, - promptTokens: existingResponse.promptTokens ?? undefined, - completionTokens: existingResponse.completionTokens ?? undefined, - }; - } else { - try { - modelResponse = await getCompletion( - prompt as unknown as CompletionCreateParams, - input.channel, - ); - } catch (e) { - console.error(e); - throw e; - } - } - - const modelOutput = await prisma.modelOutput.upsert({ - where: { - promptVariantId_testScenarioId: { - promptVariantId: input.variantId, - testScenarioId: input.scenarioId, - }, - }, - create: { - promptVariantId: input.variantId, - testScenarioId: input.scenarioId, - inputHash, - ...modelResponse, - }, - update: { - ...modelResponse, - }, - }); - - await reevaluateVariant(input.variantId); - - return modelOutput; - }), -}); diff --git a/src/server/api/routers/promptVariants.router.ts b/src/server/api/routers/promptVariants.router.ts index 943c4c1..8a57a65 100644 --- a/src/server/api/routers/promptVariants.router.ts +++ b/src/server/api/routers/promptVariants.router.ts @@ -2,6 +2,7 @@ import { isObject } from "lodash"; import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; import { prisma } from "~/server/db"; +import { generateNewCell } from "~/server/utils/generateNewCell"; import { OpenAIChatModel } from "~/server/types"; import { constructPrompt } from "~/server/utils/constructPrompt"; import userError from "~/server/utils/error"; @@ -43,17 +44,24 @@ export const promptVariantsRouter = createTRPCRouter({ visible: true, }, }); - const outputCount = await prisma.modelOutput.count({ + const outputCount = await prisma.scenarioVariantCell.count({ where: { promptVariantId: input.variantId, testScenario: { visible: true }, + modelOutput: { + isNot: null, + }, }, }); const overallTokens = await prisma.modelOutput.aggregate({ where: { - promptVariantId: input.variantId, - testScenario: { visible: true }, + scenarioVariantCell: { + promptVariantId: input.variantId, + testScenario: { + visible: true, + }, + }, }, _sum: { promptTokens: true, @@ -115,6 +123,17 @@ export const promptVariantsRouter = createTRPCRouter({ recordExperimentUpdated(input.experimentId), ]); + const scenarios = await prisma.testScenario.findMany({ + where: { + experimentId: input.experimentId, + visible: true, + }, + }); + + for (const scenario of scenarios) { + await generateNewCell(newVariant.id, scenario.id); + } + return newVariant; }), @@ -234,6 +253,17 @@ export const promptVariantsRouter = createTRPCRouter({ await prisma.$transaction([hideOldVariants, recordExperimentUpdated(existing.experimentId)]); + const scenarios = await prisma.testScenario.findMany({ + where: { + experimentId: newVariant.experimentId, + visible: true, + }, + }); + + for (const scenario of scenarios) { + await generateNewCell(newVariant.id, scenario.id); + } + return { status: "ok" } as const; }), diff --git a/src/server/api/routers/scenarioVariantCells.router.ts b/src/server/api/routers/scenarioVariantCells.router.ts new file mode 100644 index 0000000..09e1172 --- /dev/null +++ b/src/server/api/routers/scenarioVariantCells.router.ts @@ -0,0 +1,68 @@ +import { z } from "zod"; +import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; +import { prisma } from "~/server/db"; +import { generateNewCell } from "~/server/utils/generateNewCell"; +import { queueLLMRetrievalTask } from "~/server/utils/queueLLMRetrievalTask"; + +export const scenarioVariantCellsRouter = createTRPCRouter({ + get: publicProcedure + .input( + z.object({ + scenarioId: z.string(), + variantId: z.string(), + }), + ) + .query(async ({ input }) => { + return await prisma.scenarioVariantCell.findUnique({ + where: { + promptVariantId_testScenarioId: { + promptVariantId: input.variantId, + testScenarioId: input.scenarioId, + }, + }, + include: { + modelOutput: true, + }, + }); + }), + forceRefetch: publicProcedure + .input( + z.object({ + scenarioId: z.string(), + variantId: z.string(), + }), + ) + .mutation(async ({ input }) => { + const cell = await prisma.scenarioVariantCell.findUnique({ + where: { + promptVariantId_testScenarioId: { + promptVariantId: input.variantId, + testScenarioId: input.scenarioId, + }, + }, + include: { + modelOutput: true, + }, + }); + + if (!cell) { + await generateNewCell(input.variantId, input.scenarioId); + return true; + } + + if (cell.modelOutput) { + // TODO: Maybe keep these around to show previous generations? + await prisma.modelOutput.delete({ + where: { id: cell.modelOutput.id }, + }); + } + + await prisma.scenarioVariantCell.update({ + where: { id: cell.id }, + data: { retrievalStatus: "PENDING" }, + }); + + await queueLLMRetrievalTask(cell.id); + return true; + }), +}); diff --git a/src/server/api/routers/scenarios.router.ts b/src/server/api/routers/scenarios.router.ts index dbc2096..5075ae7 100644 --- a/src/server/api/routers/scenarios.router.ts +++ b/src/server/api/routers/scenarios.router.ts @@ -4,6 +4,7 @@ import { prisma } from "~/server/db"; import { autogenerateScenarioValues } from "../autogen"; import { recordExperimentUpdated } from "~/server/utils/recordExperimentUpdated"; import { reevaluateAll } from "~/server/utils/evaluations"; +import { generateNewCell } from "~/server/utils/generateNewCell"; export const scenariosRouter = createTRPCRouter({ list: publicProcedure.input(z.object({ experimentId: z.string() })).query(async ({ input }) => { @@ -48,10 +49,21 @@ export const scenariosRouter = createTRPCRouter({ }, }); - await prisma.$transaction([ + const [scenario] = await prisma.$transaction([ createNewScenarioAction, recordExperimentUpdated(input.experimentId), ]); + + const promptVariants = await prisma.promptVariant.findMany({ + where: { + experimentId: input.experimentId, + visible: true, + }, + }); + + for (const variant of promptVariants) { + await generateNewCell(variant.id, scenario.id); + } }), hide: publicProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => { @@ -175,6 +187,17 @@ export const scenariosRouter = createTRPCRouter({ }, }); + const promptVariants = await prisma.promptVariant.findMany({ + where: { + experimentId: newScenario.experimentId, + visible: true, + }, + }); + + for (const variant of promptVariants) { + await generateNewCell(variant.id, newScenario.id); + } + return newScenario; }), }); diff --git a/src/server/scripts/migrateScenarioVariantOutputData.ts b/src/server/scripts/migrateScenarioVariantOutputData.ts new file mode 100644 index 0000000..99984fa --- /dev/null +++ b/src/server/scripts/migrateScenarioVariantOutputData.ts @@ -0,0 +1,47 @@ +import { type Prisma } from "@prisma/client"; +import { prisma } from "../db"; + +async function migrateScenarioVariantOutputData() { + // Get all ScenarioVariantCells + const cells = await prisma.scenarioVariantCell.findMany({ include: { modelOutput: true } }); + console.log(`Found ${cells.length} records`); + + let updatedCount = 0; + + // Loop through all scenarioVariants + for (const cell of cells) { + // Create a new ModelOutput for each ScenarioVariant with an existing output + if (cell.output && !cell.modelOutput) { + updatedCount++; + await prisma.modelOutput.create({ + data: { + scenarioVariantCellId: cell.id, + inputHash: cell.inputHash || "", + output: cell.output as Prisma.InputJsonValue, + timeToComplete: cell.timeToComplete ?? undefined, + promptTokens: cell.promptTokens, + completionTokens: cell.completionTokens, + createdAt: cell.createdAt, + updatedAt: cell.updatedAt, + }, + }); + } else if (cell.errorMessage && cell.retrievalStatus === "COMPLETE") { + updatedCount++; + await prisma.scenarioVariantCell.update({ + where: { id: cell.id }, + data: { + retrievalStatus: "ERROR", + }, + }); + } + } + + console.log("Data migration completed"); + console.log(`Updated ${updatedCount} records`); +} + +// Execute the function +migrateScenarioVariantOutputData().catch((error) => { + console.error("An error occurred while migrating data: ", error); + process.exit(1); +}); diff --git a/src/server/tasks/defineTask.ts b/src/server/tasks/defineTask.ts new file mode 100644 index 0000000..64bd834 --- /dev/null +++ b/src/server/tasks/defineTask.ts @@ -0,0 +1,31 @@ +// Import necessary dependencies +import { quickAddJob, type Helpers, type Task } from "graphile-worker"; +import { env } from "~/env.mjs"; + +// Define the defineTask function +function defineTask( + taskIdentifier: string, + taskHandler: (payload: TPayload, helpers: Helpers) => Promise, +) { + const enqueue = async (payload: TPayload) => { + console.log("Enqueuing task", taskIdentifier, payload); + await quickAddJob({ connectionString: env.DATABASE_URL }, taskIdentifier, payload); + }; + + const handler = (payload: TPayload, helpers: Helpers) => { + helpers.logger.info(`Running task ${taskIdentifier} with payload: ${JSON.stringify(payload)}`); + return taskHandler(payload, helpers); + }; + + const task = { + identifier: taskIdentifier, + handler: handler as Task, + }; + + return { + enqueue, + task, + }; +} + +export default defineTask; diff --git a/src/server/tasks/queryLLM.task.ts b/src/server/tasks/queryLLM.task.ts new file mode 100644 index 0000000..d2178d0 --- /dev/null +++ b/src/server/tasks/queryLLM.task.ts @@ -0,0 +1,144 @@ +import crypto from "crypto"; +import { prisma } from "~/server/db"; +import defineTask from "./defineTask"; +import { type CompletionResponse, getCompletion } from "../utils/getCompletion"; +import { type JSONSerializable } from "../types"; +import { sleep } from "../utils/sleep"; +import { shouldStream } from "../utils/shouldStream"; +import { generateChannel } from "~/utils/generateChannel"; +import { reevaluateVariant } from "../utils/evaluations"; +import { constructPrompt } from "../utils/constructPrompt"; +import { type CompletionCreateParams } from "openai/resources/chat"; + +const MAX_AUTO_RETRIES = 10; +const MIN_DELAY = 500; // milliseconds +const MAX_DELAY = 15000; // milliseconds + +function calculateDelay(numPreviousTries: number): number { + const baseDelay = Math.min(MAX_DELAY, MIN_DELAY * Math.pow(2, numPreviousTries)); + const jitter = Math.random() * baseDelay; + return baseDelay + jitter; +} + +const getCompletionWithRetries = async ( + cellId: string, + payload: JSONSerializable, + channel?: string, +): Promise => { + for (let i = 0; i < MAX_AUTO_RETRIES; i++) { + const modelResponse = await getCompletion( + payload as unknown as CompletionCreateParams, + channel, + ); + if (modelResponse.statusCode !== 429 || i === MAX_AUTO_RETRIES - 1) { + return modelResponse; + } + const delay = calculateDelay(i); + await prisma.scenarioVariantCell.update({ + where: { id: cellId }, + data: { + errorMessage: "Rate limit exceeded", + statusCode: 429, + retryTime: new Date(Date.now() + delay), + }, + }); + // TODO: Maybe requeue the job so other jobs can run in the future? + await sleep(delay); + } + throw new Error("Max retries limit reached"); +}; + +export type queryLLMJob = { + scenarioVariantCellId: string; +}; + +export const queryLLM = defineTask("queryLLM", async (task) => { + const { scenarioVariantCellId } = task; + const cell = await prisma.scenarioVariantCell.findUnique({ + where: { id: scenarioVariantCellId }, + include: { modelOutput: true }, + }); + if (!cell) { + return; + } + + // If cell is not pending, then some other job is already processing it + if (cell.retrievalStatus !== "PENDING") { + return; + } + await prisma.scenarioVariantCell.update({ + where: { id: scenarioVariantCellId }, + data: { + retrievalStatus: "IN_PROGRESS", + }, + }); + + const variant = await prisma.promptVariant.findUnique({ + where: { id: cell.promptVariantId }, + }); + if (!variant) { + return; + } + + const scenario = await prisma.testScenario.findUnique({ + where: { id: cell.testScenarioId }, + }); + if (!scenario) { + return; + } + + const prompt = await constructPrompt(variant, scenario.variableValues); + + const streamingEnabled = shouldStream(prompt); + let streamingChannel; + + if (streamingEnabled) { + streamingChannel = generateChannel(); + // Save streaming channel so that UI can connect to it + await prisma.scenarioVariantCell.update({ + where: { id: scenarioVariantCellId }, + data: { + streamingChannel, + }, + }); + } + + const modelResponse = await getCompletionWithRetries( + scenarioVariantCellId, + prompt, + streamingChannel, + ); + + let modelOutput = null; + if (modelResponse.statusCode === 200) { + const inputHash = crypto.createHash("sha256").update(JSON.stringify(prompt)).digest("hex"); + + modelOutput = await prisma.modelOutput.create({ + data: { + scenarioVariantCellId, + inputHash, + output: modelResponse.output, + timeToComplete: modelResponse.timeToComplete, + promptTokens: modelResponse.promptTokens, + completionTokens: modelResponse.completionTokens, + }, + }); + } + + await prisma.scenarioVariantCell.update({ + where: { id: scenarioVariantCellId }, + data: { + statusCode: modelResponse.statusCode, + errorMessage: modelResponse.errorMessage, + streamingChannel: null, + retrievalStatus: modelOutput ? "COMPLETE" : "ERROR", + modelOutput: { + connect: { + id: modelOutput?.id, + }, + }, + }, + }); + + await reevaluateVariant(cell.promptVariantId); +}); diff --git a/src/server/tasks/worker.ts b/src/server/tasks/worker.ts new file mode 100644 index 0000000..e2fc916 --- /dev/null +++ b/src/server/tasks/worker.ts @@ -0,0 +1,40 @@ +import { type TaskList, run } from "graphile-worker"; +import "dotenv/config"; + +import { env } from "~/env.mjs"; +import { queryLLM } from "./queryLLM.task"; + +const registeredTasks = [queryLLM]; + +const taskList = registeredTasks.reduce((acc, task) => { + acc[task.task.identifier] = task.task.handler; + return acc; +}, {} as TaskList); + +async function main() { + // Run a worker to execute jobs: + const runner = await run({ + connectionString: env.DATABASE_URL, + concurrency: 20, + // Install signal handlers for graceful shutdown on SIGINT, SIGTERM, etc + noHandleSignals: false, + pollInterval: 1000, + // you can set the taskList or taskDirectory but not both + taskList, + // or: + // taskDirectory: `${__dirname}/tasks`, + }); + + // Immediately await (or otherwise handled) the resulting promise, to avoid + // "unhandled rejection" errors causing a process crash in the event of + // something going wrong. + await runner.promise; + + // If the worker exits (whether through fatal error or otherwise), the above + // promise will resolve/reject. +} + +main().catch((err) => { + console.error("Unhandled error occurred running worker: ", err); + process.exit(1); +}); diff --git a/src/server/utils/evaluations.ts b/src/server/utils/evaluations.ts index 690cad8..3b24334 100644 --- a/src/server/utils/evaluations.ts +++ b/src/server/utils/evaluations.ts @@ -1,4 +1,4 @@ -import { type Evaluation } from "@prisma/client"; +import { type ModelOutput, type Evaluation } from "@prisma/client"; import { prisma } from "../db"; import { evaluateOutput } from "./evaluateOutput"; @@ -12,21 +12,22 @@ export const reevaluateVariant = async (variantId: string) => { where: { experimentId: variant.experimentId }, }); - const modelOutputs = await prisma.modelOutput.findMany({ + const cells = await prisma.scenarioVariantCell.findMany({ where: { promptVariantId: variantId, - statusCode: { notIn: [429] }, + retrievalStatus: "COMPLETE", testScenario: { visible: true }, + modelOutput: { isNot: null }, }, - include: { testScenario: true }, + include: { testScenario: true, modelOutput: true }, }); await Promise.all( evaluations.map(async (evaluation) => { - const passCount = modelOutputs.filter((output) => - evaluateOutput(output, output.testScenario, evaluation), + const passCount = cells.filter((cell) => + evaluateOutput(cell.modelOutput as ModelOutput, cell.testScenario, evaluation), ).length; - const failCount = modelOutputs.length - passCount; + const failCount = cells.length - passCount; await prisma.evaluationResult.upsert({ where: { @@ -55,22 +56,23 @@ export const reevaluateEvaluation = async (evaluation: Evaluation) => { where: { experimentId: evaluation.experimentId, visible: true }, }); - const modelOutputs = await prisma.modelOutput.findMany({ + const cells = await prisma.scenarioVariantCell.findMany({ where: { promptVariantId: { in: variants.map((v) => v.id) }, testScenario: { visible: true }, statusCode: { notIn: [429] }, + modelOutput: { isNot: null }, }, - include: { testScenario: true }, + include: { testScenario: true, modelOutput: true }, }); await Promise.all( variants.map(async (variant) => { - const outputs = modelOutputs.filter((output) => output.promptVariantId === variant.id); - const passCount = outputs.filter((output) => - evaluateOutput(output, output.testScenario, evaluation), + const variantCells = cells.filter((cell) => cell.promptVariantId === variant.id); + const passCount = variantCells.filter((cell) => + evaluateOutput(cell.modelOutput as ModelOutput, cell.testScenario, evaluation), ).length; - const failCount = outputs.length - passCount; + const failCount = variantCells.length - passCount; await prisma.evaluationResult.upsert({ where: { diff --git a/src/server/utils/generateNewCell.ts b/src/server/utils/generateNewCell.ts new file mode 100644 index 0000000..f55adfe --- /dev/null +++ b/src/server/utils/generateNewCell.ts @@ -0,0 +1,76 @@ +import crypto from "crypto"; +import { type Prisma } from "@prisma/client"; +import { prisma } from "../db"; +import { queueLLMRetrievalTask } from "./queueLLMRetrievalTask"; +import { constructPrompt } from "./constructPrompt"; + +export const generateNewCell = async (variantId: string, scenarioId: string) => { + const variant = await prisma.promptVariant.findUnique({ + where: { + id: variantId, + }, + }); + + const scenario = await prisma.testScenario.findUnique({ + where: { + id: scenarioId, + }, + }); + + if (!variant || !scenario) return null; + + const prompt = await constructPrompt(variant, scenario.variableValues); + + const inputHash = crypto.createHash("sha256").update(JSON.stringify(prompt)).digest("hex"); + + let cell = await prisma.scenarioVariantCell.findUnique({ + where: { + promptVariantId_testScenarioId: { + promptVariantId: variantId, + testScenarioId: scenarioId, + }, + }, + include: { + modelOutput: true, + }, + }); + + if (cell) return cell; + + cell = await prisma.scenarioVariantCell.create({ + data: { + promptVariantId: variantId, + testScenarioId: scenarioId, + }, + include: { + modelOutput: true, + }, + }); + + const matchingModelOutput = await prisma.modelOutput.findFirst({ + where: { + inputHash, + }, + }); + + let newModelOutput; + + if (matchingModelOutput) { + newModelOutput = await prisma.modelOutput.create({ + data: { + scenarioVariantCellId: cell.id, + inputHash, + output: matchingModelOutput.output as Prisma.InputJsonValue, + timeToComplete: matchingModelOutput.timeToComplete, + promptTokens: matchingModelOutput.promptTokens, + completionTokens: matchingModelOutput.completionTokens, + createdAt: matchingModelOutput.createdAt, + updatedAt: matchingModelOutput.updatedAt, + }, + }); + } else { + cell = await queueLLMRetrievalTask(cell.id); + } + + return { ...cell, modelOutput: newModelOutput }; +}; diff --git a/src/server/utils/getCompletion.ts b/src/server/utils/getCompletion.ts index 61452e4..32d7b61 100644 --- a/src/server/utils/getCompletion.ts +++ b/src/server/utils/getCompletion.ts @@ -9,7 +9,7 @@ import { env } from "~/env.mjs"; import { countOpenAIChatTokens } from "~/utils/countTokens"; import { rateLimitErrorMessage } from "~/sharedStrings"; -type CompletionResponse = { +export type CompletionResponse = { output: Prisma.InputJsonValue | typeof Prisma.JsonNull; statusCode: number; errorMessage: string | null; diff --git a/src/server/utils/queueLLMRetrievalTask.ts b/src/server/utils/queueLLMRetrievalTask.ts new file mode 100644 index 0000000..762b708 --- /dev/null +++ b/src/server/utils/queueLLMRetrievalTask.ts @@ -0,0 +1,22 @@ +import { prisma } from "../db"; +import { queryLLM } from "../tasks/queryLLM.task"; + +export const queueLLMRetrievalTask = async (cellId: string) => { + const updatedCell = await prisma.scenarioVariantCell.update({ + where: { + id: cellId, + }, + data: { + retrievalStatus: "PENDING", + errorMessage: null, + }, + include: { + modelOutput: true, + }, + }); + + // @ts-expect-error we aren't passing the helpers but that's ok + void queryLLM.task.handler({ scenarioVariantCellId: cellId }, { logger: console }); + + return updatedCell; +}; diff --git a/src/server/utils/shouldStream.ts b/src/server/utils/shouldStream.ts new file mode 100644 index 0000000..8dd0a38 --- /dev/null +++ b/src/server/utils/shouldStream.ts @@ -0,0 +1,7 @@ +import { isObject } from "lodash"; +import { type JSONSerializable } from "../types"; + +export const shouldStream = (config: JSONSerializable): boolean => { + const shouldStream = isObject(config) && "stream" in config && config.stream === true; + return shouldStream; +}; diff --git a/src/server/utils/sleep.ts b/src/server/utils/sleep.ts new file mode 100644 index 0000000..fe57bb6 --- /dev/null +++ b/src/server/utils/sleep.ts @@ -0,0 +1 @@ +export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/utils/useSocket.ts b/src/utils/useSocket.ts index ee0738c..175036c 100644 --- a/src/utils/useSocket.ts +++ b/src/utils/useSocket.ts @@ -5,7 +5,7 @@ import { env } from "~/env.mjs"; const url = env.NEXT_PUBLIC_SOCKET_URL; -export default function useSocket(channel?: string) { +export default function useSocket(channel?: string | null) { const socketRef = useRef(); const [message, setMessage] = useState(null);