Compare commits

..

17 Commits

Author SHA1 Message Date
Kyle Corbitt
8d1609dd52 Add admin role
Allow privileged users to administer the system.
2023-08-03 09:35:13 -07:00
David Corbitt
f3380f302d Simplify world champs screen 2023-08-02 23:57:44 -07:00
David Corbitt
3dba9c7ee1 Update posthog version 2023-08-02 23:30:15 -07:00
David Corbitt
e0e4f7a9d6 Fix mobile table padding 2023-08-02 23:08:49 -07:00
arcticfly
48293dc579 Add link to demo experiment (#114) 2023-08-02 22:50:09 -07:00
arcticfly
38ac6243a0 Add server posthog events (#113) 2023-08-02 14:21:07 -07:00
arcticfly
bd2f58e2a5 Improve posthog (#112)
* Add SessionIdentifier

* Identify by id

* Rewrite posthog events

* Add NEXT_PUBLIC_HOST to dockerfile

* Fix default url

* Move SessionIdentifier into analytics file
2023-08-02 13:30:25 -07:00
Kyle Corbitt
808e47c6b9 Merge pull request #111 from OpenPipe/gh-btn
Update TopNavbar component to include a GitHub button
2023-08-02 10:15:26 -07:00
Kyle Corbitt
5945f0ed6b Update TopNavbar component to include a GitHub button 2023-08-02 10:11:41 -07:00
arcticfly
6bc7d76d15 Update README.md 2023-08-02 00:59:05 -07:00
arcticfly
e9ed173e34 Update README.md 2023-08-02 00:57:24 -07:00
arcticfly
75d58d7021 Update README.md 2023-08-02 00:56:19 -07:00
arcticfly
896c8c5c57 Update README.md 2023-08-02 00:51:57 -07:00
arcticfly
ec5547d0b0 Update README.md with new features and gifs (#110) 2023-08-02 00:46:48 -07:00
Kyle Corbitt
77e4e3b8c3 mobile styles 2023-08-01 23:08:35 -07:00
Kyle Corbitt
a1b03ddad1 Merge pull request #109 from OpenPipe/debug-prompts
Add debug modal for output cells
2023-08-01 22:51:39 -07:00
arcticfly
72c70e2a55 Improve conversion to/from Claude (#108)
* Increase min width of prompt variant

* Increase width of custom instructions input

* Start recording API docs

* Provide better instructions for converting to/from Claude

* Fix prettier
2023-08-01 21:03:23 -07:00
23 changed files with 327 additions and 162 deletions

View File

@@ -26,6 +26,8 @@ NEXT_PUBLIC_SOCKET_URL="http://localhost:3318"
NEXTAUTH_SECRET="your_secret"
NEXTAUTH_URL="http://localhost:3000"
NEXT_PUBLIC_HOST="http://localhost:3000"
# Next Auth Github Provider
GITHUB_CLIENT_ID="your_client_id"
GITHUB_CLIENT_SECRET="your_secret"

View File

@@ -20,6 +20,7 @@ FROM base as builder
# Include all NEXT_PUBLIC_* env vars here
ARG NEXT_PUBLIC_POSTHOG_KEY
ARG NEXT_PUBLIC_SOCKET_URL
ARG NEXT_PUBLIC_HOST
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules

View File

@@ -6,40 +6,47 @@ OpenPipe is a flexible playground for comparing and optimizing LLM prompts. It l
## Sample Experiments
These are simple experiments users have created that show how OpenPipe works.
These are simple experiments users have created that show how OpenPipe works. Feel free to fork them and start experimenting yourself.
- [Country Capitals](https://app.openpipe.ai/experiments/11111111-1111-1111-1111-111111111111)
- [Twitter Sentiment Analysis](https://app.openpipe.ai/experiments/62c20a73-2012-4a64-973c-4b665ad46a57)
- [Reddit User Needs](https://app.openpipe.ai/experiments/22222222-2222-2222-2222-222222222222)
- [OpenAI Function Calls](https://app.openpipe.ai/experiments/2ebbdcb3-ed51-456e-87dc-91f72eaf3e2b)
- [Activity Classification](https://app.openpipe.ai/experiments/3950940f-ab6b-4b74-841d-7e9dbc4e4ff8)
<img src="https://github.com/openpipe/openpipe/assets/176426/fc7624c6-5b65-4d4d-82b7-4a816f3e5678" alt="demo" height="400px">
<img src="https://github.com/openpipe/openpipe/assets/41524992/219a844e-3f4e-4f6b-8066-41348b42977b" alt="demo">
You can use our hosted version of OpenPipe at [https://openpipe.ai]. You can also clone this repository and [run it locally](#running-locally).
You can use our hosted version of OpenPipe at https://openpipe.ai. You can also clone this repository and [run it locally](#running-locally).
## High-Level Features
**Configure Multiple Prompts**
Set up multiple prompt configurations and compare their output side-by-side. Each configuration can be configured independently.
**Visualize Responses**
Inspect prompt completions side-by-side.
**Test Many Inputs**
OpenPipe lets you _template_ a prompt. Use the templating feature to run the prompts you're testing against many potential inputs for broader coverage of your problem space than you'd get with manual testing.
<br>
**Test Many Inputs**
OpenPipe lets you _template_ a prompt. Use the templating feature to run the prompts you're testing against many potential inputs for broad coverage of your problem space.
<br>
**Translate between Model APIs**
Write your prompt in one format and automatically convert it to work with any other model.
<img width="480" alt="Screenshot 2023-08-01 at 11 55 38 PM" src="https://github.com/OpenPipe/OpenPipe/assets/41524992/1e19ccf2-96b6-4e93-a3a5-1449710d1b5b" alt="translate between models">
<br><br>
**Refine your prompts automatically**
Use a growing database of best-practice refinements to improve your prompts automatically.
<img width="480" alt="Screenshot 2023-08-01 at 11 55 38 PM" src="https://github.com/OpenPipe/OpenPipe/assets/41524992/87a27fe7-daef-445c-a5e2-1c82b23f9f99" alt="add function call">
<br><br>
**🪄 Auto-generate Test Scenarios**
OpenPipe includes a tool to generate new test scenarios based on your existing prompts and scenarios. Just click "Autogenerate Scenario" to try it out!
**Prompt Validation and Typeahead**
We use OpenAI's OpenAPI spec to automatically provide typeahead and validate prompts.
<img width="600" src="https://github.com/openpipe/openpipe/assets/41524992/219a844e-3f4e-4f6b-8066-41348b42977b" alt="auto-generate">
<img alt="typeahead" src="https://github.com/openpipe/openpipe/assets/176426/acc638f8-d851-4742-8d01-fe6f98890840" height="300px">
**Function Call Support**
Natively supports [OpenAI function calls](https://openai.com/blog/function-calling-and-other-api-updates) on supported models.
<img height="300px" alt="function calls" src="https://github.com/openpipe/openpipe/assets/176426/48ad13fe-af2f-4294-bf32-62015597fd9b">
<br><br>
## Supported Models

View File

@@ -21,6 +21,13 @@ const config = {
defaultLocale: "en",
},
rewrites: async () => [
{
source: "/ingest/:path*",
destination: "https://app.posthog.com/:path*",
},
],
webpack: (config) => {
config.module.rules.push({
test: /\.txt$/,

View File

@@ -67,12 +67,14 @@
"nextjs-routes": "^2.0.1",
"openai": "4.0.0-beta.7",
"pluralize": "^8.0.0",
"posthog-js": "^1.68.4",
"posthog-js": "^1.75.3",
"posthog-node": "^3.1.1",
"prettier": "^3.0.0",
"prismjs": "^1.29.0",
"react": "18.2.0",
"react-diff-viewer": "^3.1.1",
"react-dom": "18.2.0",
"react-github-btn": "^1.4.0",
"react-icons": "^4.10.1",
"react-json-tree": "^0.18.0",
"react-select": "^5.7.4",

124
pnpm-lock.yaml generated
View File

@@ -1,4 +1,4 @@
lockfileVersion: '6.1'
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
@@ -144,8 +144,11 @@ dependencies:
specifier: ^8.0.0
version: 8.0.0
posthog-js:
specifier: ^1.68.4
version: 1.68.4
specifier: ^1.75.3
version: 1.75.3
posthog-node:
specifier: ^3.1.1
version: 3.1.1
prettier:
specifier: ^3.0.0
version: 3.0.0
@@ -161,6 +164,9 @@ dependencies:
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
react-github-btn:
specifier: ^1.4.0
version: 1.4.0(react@18.2.0)
react-icons:
specifier: ^4.10.1
version: 4.10.1(react@18.2.0)
@@ -2893,7 +2899,7 @@ packages:
/@types/eslint-scope@3.7.4:
resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
dependencies:
'@types/eslint': 8.37.0
'@types/eslint': 8.44.1
'@types/estree': 1.0.1
dev: true
@@ -2904,6 +2910,13 @@ packages:
'@types/json-schema': 7.0.12
dev: true
/@types/eslint@8.44.1:
resolution: {integrity: sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==}
dependencies:
'@types/estree': 1.0.1
'@types/json-schema': 7.0.12
dev: true
/@types/estree@1.0.1:
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
dev: true
@@ -2997,6 +3010,10 @@ packages:
/@types/node@18.16.0:
resolution: {integrity: sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==}
/@types/node@18.17.1:
resolution: {integrity: sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==}
dev: true
/@types/node@20.4.2:
resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==}
dev: true
@@ -3627,6 +3644,15 @@ packages:
engines: {node: '>=4'}
dev: true
/axios@0.27.2:
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
dependencies:
follow-redirects: 1.15.2
form-data: 4.0.0
transitivePeerDependencies:
- debug
dev: false
/axobject-query@3.2.1:
resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==}
dependencies:
@@ -3747,6 +3773,17 @@ packages:
dependencies:
fill-range: 7.0.1
/browserslist@4.21.10:
resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001519
electron-to-chromium: 1.4.482
node-releases: 2.0.13
update-browserslist-db: 1.0.11(browserslist@4.21.10)
dev: true
/browserslist@4.21.9:
resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -3813,6 +3850,10 @@ packages:
/caniuse-lite@1.0.30001517:
resolution: {integrity: sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==}
/caniuse-lite@1.0.30001519:
resolution: {integrity: sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==}
dev: true
/chai@4.3.7:
resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==}
engines: {node: '>=4'}
@@ -4323,6 +4364,10 @@ packages:
/electron-to-chromium@1.4.465:
resolution: {integrity: sha512-XQcuHvEJRMU97UJ75e170mgcITZoz0lIyiaVjk6R+NMTJ8KBIvUHYd1779swgOppUlzxR+JsLpq59PumaXS1jQ==}
/electron-to-chromium@1.4.482:
resolution: {integrity: sha512-h+UqpfmEr1Qkk0zp7ej/jid7CXoq4m4QzW6wNTb0ELJ/BZCpA4wgUylBIMGCe621tnr4l5VmoHjdoSx2lbnNJA==}
dev: true
/emoji-regex@10.2.1:
resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==}
dev: false
@@ -5105,6 +5150,16 @@ packages:
tslib: 2.6.0
dev: false
/follow-redirects@1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: false
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies:
@@ -5123,6 +5178,15 @@ packages:
mime-types: 2.1.35
dev: false
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/format@0.2.2:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'}
@@ -5246,6 +5310,10 @@ packages:
dependencies:
resolve-pkg-maps: 1.0.0
/github-buttons@2.27.0:
resolution: {integrity: sha512-PmfRMI2Rttg/2jDfKBeSl621sEznrsKF019SuoLdoNlO7qRUZaOyEI5Li4uW+79pVqnDtKfIEVuHTIJ5lgy64w==}
dev: false
/glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -5788,7 +5856,7 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 18.16.0
'@types/node': 18.17.1
merge-stream: 2.0.0
supports-color: 8.1.1
dev: true
@@ -6771,12 +6839,22 @@ packages:
resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==}
dev: false
/posthog-js@1.68.4:
resolution: {integrity: sha512-rHk4uk99nvWiDTU7P2mFdEfzFR6km0hvOpCR3tm/+F7kCJKs7QDkMblOZZHZultxM4wSNyB4neeohmnHjKYUhQ==}
/posthog-js@1.75.3:
resolution: {integrity: sha512-q5xP4R/Tx8E6H0goZQjY+URMLATFiYXc2raHA+31aNvpBs118fPTmExa4RK6MgRZDFhBiMUBZNT6aj7dM3SyUQ==}
dependencies:
fflate: 0.4.8
dev: false
/posthog-node@3.1.1:
resolution: {integrity: sha512-OUSYcnLHbzvY/dxNsbUGoYuTZz5XNx48BkfiCkOIJZMFvot5VPQ0KWEjX+kzYxEwHeXbjW9plqsOVcYCYfidgg==}
engines: {node: '>=15.0.0'}
dependencies:
axios: 0.27.2
rusha: 0.8.14
transitivePeerDependencies:
- debug
dev: false
/preact-render-to-string@5.2.6(preact@10.16.0):
resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==}
peerDependencies:
@@ -6981,6 +7059,15 @@ packages:
use-sidecar: 1.1.2(@types/react@18.2.6)(react@18.2.0)
dev: false
/react-github-btn@1.4.0(react@18.2.0):
resolution: {integrity: sha512-lV4FYClAfjWnBfv0iNlJUGhamDgIq6TayD0kPZED6VzHWdpcHmPfsYOZ/CFwLfPv4Zp+F4m8QKTj0oy2HjiGXg==}
peerDependencies:
react: '>=16.3.0'
dependencies:
github-buttons: 2.27.0
react: 18.2.0
dev: false
/react-icons@4.10.1(react@18.2.0):
resolution: {integrity: sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw==}
peerDependencies:
@@ -7264,6 +7351,10 @@ packages:
queue-microtask: 1.2.3
dev: true
/rusha@0.8.14:
resolution: {integrity: sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==}
dev: false
/rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
dependencies:
@@ -7721,12 +7812,12 @@ packages:
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.1
terser: 5.19.1
terser: 5.19.2
webpack: 5.88.2
dev: true
/terser@5.19.1:
resolution: {integrity: sha512-27hxBUVdV6GoNg1pKQ7Z5cbR6V9txPVyBA+FQw3BaZ1Wuzvztce5p156DaP0NVZNrMZZ+6iG9Syf7WgMNKDg2Q==}
/terser@5.19.2:
resolution: {integrity: sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==}
engines: {node: '>=10'}
hasBin: true
dependencies:
@@ -8005,6 +8096,17 @@ packages:
engines: {node: '>=8'}
dev: true
/update-browserslist-db@1.0.11(browserslist@4.21.10):
resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
dependencies:
browserslist: 4.21.10
escalade: 3.1.1
picocolors: 1.0.0
dev: true
/update-browserslist-db@1.0.11(browserslist@4.21.9):
resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==}
hasBin: true
@@ -8325,7 +8427,7 @@ packages:
'@webassemblyjs/wasm-parser': 1.11.6
acorn: 8.10.0
acorn-import-assertions: 1.9.0(acorn@8.10.0)
browserslist: 4.21.9
browserslist: 4.21.10
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.3.0

View File

@@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('ADMIN', 'USER');
-- AlterTable
ALTER TABLE "User" ADD COLUMN "role" "UserRole" NOT NULL DEFAULT 'USER';

View File

@@ -249,12 +249,20 @@ model Session {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
enum UserRole {
ADMIN
USER
}
model User {
id String @id @default(uuid()) @db.Uuid
name String?
email String? @unique
emailVerified DateTime?
image String?
id String @id @default(uuid()) @db.Uuid
name String?
email String? @unique
emailVerified DateTime?
image String?
role UserRole @default(USER)
accounts Account[]
sessions Session[]
organizationUsers OrganizationUser[]

View File

@@ -88,7 +88,7 @@ export default function OutputCell({
)}
</VStack>
),
[hardRefetching, hardRefetch, mostRecentResponse, scenario],
[hardRefetching, hardRefetch, mostRecentResponse, scenario, cell],
);
if (!vars) return null;

View File

@@ -35,7 +35,7 @@ export default function OutputsTable({ experimentId }: { experimentId: string |
pb={24}
pl={8}
display="grid"
gridTemplateColumns={`250px repeat(${variants.data.length}, minmax(300px, 1fr)) auto`}
gridTemplateColumns={`250px repeat(${variants.data.length}, minmax(360px, 1fr)) auto`}
sx={{
"> *": {
borderColor: "gray.300",

View File

@@ -97,7 +97,7 @@ export const RefinePromptModal = ({
<ModalCloseButton />
<ModalBody maxW="unset">
<VStack spacing={8}>
<VStack spacing={4}>
<VStack spacing={4} w="full">
{Object.keys(refinementActions).length && (
<>
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={8}>

View File

@@ -29,6 +29,7 @@ export const env = createEnv({
client: {
NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(),
NEXT_PUBLIC_SOCKET_URL: z.string().url().default("http://localhost:3318"),
NEXT_PUBLIC_HOST: z.string().url().default("http://localhost:3000"),
},
/**
@@ -42,6 +43,7 @@ export const env = createEnv({
RESTRICT_PRISMA_LOGS: process.env.RESTRICT_PRISMA_LOGS,
NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
NEXT_PUBLIC_SOCKET_URL: process.env.NEXT_PUBLIC_SOCKET_URL,
NEXT_PUBLIC_HOST: process.env.NEXT_PUBLIC_HOST,
GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
REPLICATE_API_TOKEN: process.env.REPLICATE_API_TOKEN,

View File

@@ -2,7 +2,7 @@
"type": "object",
"properties": {
"model": {
"description": "The model that will complete your prompt.\nAs we improve Claude, we develop new versions of it that you can query.\nThis parameter controls which version of Claude answers your request.\nRight now we are offering two model families: Claude, and Claude Instant.\nYou can use them by setting model to \"claude-2\" or \"claude-instant-1\", respectively.\nSee models for additional details.\n",
"description": "The model that will complete your prompt.",
"x-oaiTypeLabel": "string",
"type": "string",
"enum": [
@@ -13,116 +13,50 @@
]
},
"prompt": {
"description": "The prompt that you want Claude to complete.\n\nFor proper response generation you will need to format your prompt as follows:\n\\n\\nHuman: ${userQuestion}\\n\\nAssistant:\nSee our comments on prompts for more context.\n",
"description": "The prompt that you want Claude to complete.\n\nFor proper response generation you will need to format your prompt as follows:\n\"\\n\\nHuman: all instructions for the assistant\\n\\nAssistant:\". The prompt string should begin with the characters \"Human:\" and end with \"Assistant:\".",
"default": "<|endoftext|>",
"nullable": true,
"oneOf": [
{
"type": "string",
"default": "",
"example": "This is a test."
},
{
"type": "array",
"items": {
"type": "string",
"default": "",
"example": "This is a test."
}
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "integer"
},
"example": "[1212, 318, 257, 1332, 13]"
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "integer"
}
},
"example": "[[1212, 318, 257, 1332, 13]]"
}
]
"example": "\\n\\nHuman: What is the correct translation of ${scenario.input}? I would like a long analysis followed by a short answer.\\n\\nAssistant:",
"type": "string"
},
"max_tokens_to_sample": {
"type": "integer",
"minimum": 1,
"default": 256,
"example": 256,
"nullable": true,
"description": "The maximum number of tokens to generate before stopping.\n\nNote that our models may stop before reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate.\n"
"description": "The maximum number of tokens to generate before stopping."
},
"temperature": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 1,
"example": 1,
"nullable": true,
"description": "Amount of randomness injected into the response.\n\nDefaults to 1. Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks.\n"
"description": "Amount of randomness injected into the response.\n\nDefaults to 1."
},
"top_p": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 1,
"example": 1,
"nullable": true,
"description": "Use nucleus sampling.\n\nIn nucleus sampling, we compute the cumulative distribution over all the options \nfor each subsequent token in decreasing probability order and cut it off once \nit reaches a particular probability specified by top_p. You should either alter temperature or top_p, but not both.\n"
"description": "Use nucleus sampling.\n\nYou should either alter temperature or top_p, but not both.\n"
},
"top_k": {
"type": "number",
"minimum": 0,
"default": 5,
"example": 5,
"nullable": true,
"description": "Only sample from the top K options for each subsequent token.\n\nUsed to remove \"long tail\" low probability responses. Learn more technical details here.\n"
"description": "Only sample from the top K options for each subsequent token."
},
"stream": {
"description": "Whether to incrementally stream the response using server-sent events.\nSee this guide to SSE events for details.type: boolean\n",
"description": "Whether to incrementally stream the response using server-sent events.",
"type": "boolean",
"nullable": true,
"default": false
},
"stop_sequences": {
"description": "Sequences that will cause the model to stop generating completion text.\nOur models stop on \"\\n\\nHuman:\", and may include additional built-in stop sequences in the future. By providing the stop_sequences parameter, you may include additional strings that will cause the model to stop generating.\n",
"description": "Sequences that will cause the model to stop generating completion text.\nBy default, our models stop on \"\\n\\nHuman:\".",
"default": null,
"nullable": true,
"oneOf": [
{
"type": "string",
"default": "<|endoftext|>",
"example": "\n",
"nullable": true
},
{
"type": "array",
"minItems": 1,
"maxItems": 4,
"items": {
"type": "string",
"example": "[\"\\n\"]"
}
}
]
},
"metadata": {
"type": "object",
"properties": {
"user_id": {
"type": "string",
"example": "13803d75-b4b5-4c3e-b2a2-6f21399b021b",
"description": "An external identifier for the user who is associated with the request.\n\nThis should be a uuid, hash value, or other opaque identifier. Anthropic may use this id to help detect abuse. \nDo not include any identifying information such as name, email address, or phone number.\n"
}
},
"description": "An object describing metadata about the request.\n"
"type": "array"
}
},
"required": ["model", "prompt", "max_tokens_to_sample"]

View File

@@ -15,6 +15,7 @@ const frontendModelProvider: FrontendModelProvider<SupportedModel, Completion> =
speed: "medium",
provider: "anthropic",
learnMoreUrl: "https://www.anthropic.com/product",
apiDocsUrl: "https://docs.anthropic.com/claude/reference/complete_post",
},
"claude-instant-1.1": {
name: "Claude Instant 1.1",
@@ -24,6 +25,7 @@ const frontendModelProvider: FrontendModelProvider<SupportedModel, Completion> =
speed: "fast",
provider: "anthropic",
learnMoreUrl: "https://www.anthropic.com/product",
apiDocsUrl: "https://docs.anthropic.com/claude/reference/complete_post",
},
},

View File

@@ -21,6 +21,7 @@ export type Model = {
provider: SupportedProvider;
description?: string;
learnMoreUrl?: string;
apiDocsUrl?: string;
};
export type ProviderModel = { provider: z.infer<typeof ZodSupportedProvider>; model: string };

View File

@@ -3,12 +3,12 @@ import { SessionProvider } from "next-auth/react";
import { type AppType } from "next/app";
import { api } from "~/utils/api";
import Favicon from "~/components/Favicon";
import "~/utils/analytics";
import Head from "next/head";
import { ChakraThemeProvider } from "~/theme/ChakraThemeProvider";
import { SyncAppStore } from "~/state/sync";
import NextAdapterApp from "next-query-params/app";
import { QueryParamProvider } from "use-query-params";
import { SessionIdentifier } from "~/utils/analytics/clientAnalytics";
const MyApp: AppType<{ session: Session | null }> = ({
Component,
@@ -36,6 +36,7 @@ const MyApp: AppType<{ session: Session | null }> = ({
<SessionProvider session={session}>
<SyncAppStore />
<Favicon />
<SessionIdentifier />
<ChakraThemeProvider>
<QueryParamProvider adapter={NextAdapterApp}>
<Component {...pageProps} />

View File

@@ -18,6 +18,7 @@ import {
VStack,
useInterval,
Image,
Flex,
} from "@chakra-ui/react";
import { signIn, useSession } from "next-auth/react";
import Head from "next/head";
@@ -27,19 +28,40 @@ import UserMenu from "~/components/nav/UserMenu";
import { api } from "~/utils/api";
import dayjs from "~/utils/dayjs";
import { useHandledAsyncCallback } from "~/utils/hooks";
import GitHubButton from "react-github-btn";
const TopNavbar = () => (
<DarkMode>
<GlobalStyle />
<HStack px={4} py={2}>
<HStack as={Link} href="/" _hover={{ textDecoration: "none" }} spacing={0} py={2} pr={16}>
<Image src="/logo.svg" alt="" boxSize={6} mr={4} />
<Heading size="md" fontFamily="inconsolata, monospace">
OpenPipe
</Heading>
</HStack>
<HStack px={4} py={2} align="center" justify="center">
<HStack
as={Link}
href="/"
_hover={{ textDecoration: "none" }}
spacing={0}
py={2}
pr={16}
flex={1}
sx={{
".widget": {
display: "block",
},
}}
>
<Image src="/logo.svg" alt="" boxSize={6} mr={4} />
<Heading size="md" fontFamily="inconsolata, monospace">
OpenPipe
</Heading>
</HStack>
</DarkMode>
<Box pt="6px">
<GitHubButton
href="https://github.com/openpipe/openpipe"
data-color-scheme="no-preference: dark; light: dark; dark: dark;"
data-size="large"
aria-label="Follow @openpipe on GitHub"
>
Github
</GitHubButton>
</Box>
</HStack>
);
// Shows how long until the competition starts. Refreshes every second
@@ -103,8 +125,16 @@ function ApplicationStatus(props: BoxProps) {
} else if (user) {
return (
<Wrapper>
<HStack spacing={8}>
<UserMenu user={user} borderRadius={2} borderColor={"gray.700"} borderWidth={1} pr={6} />
<Flex flexDirection={{ base: "column", md: "row" }} alignItems="center">
<UserMenu
user={user}
borderRadius={2}
borderColor={"gray.700"}
borderWidth={1}
pr={6}
mr={{ base: 0, md: 8 }}
mb={{ base: 8, md: 0 }}
/>
<Box flex={1}>
{entrant?.approved ? (
<Text fontSize="sm">
@@ -112,7 +142,11 @@ function ApplicationStatus(props: BoxProps) {
</Text>
) : entrant ? (
<Text fontSize="sm">
Application submitted successfully! We'll notify you by email before August 14th.
Application submitted successfully. We'll notify you by email before August 14th.{" "}
<Link href="https://github.com/openpipe/openpipe" isExternal textDecor="underline">
Star our Github
</Link>{" "}
for updates while you wait!
</Text>
) : (
<Button onClick={onApply} colorScheme="orange">
@@ -120,7 +154,7 @@ function ApplicationStatus(props: BoxProps) {
</Button>
)}
</Box>
</HStack>
</Flex>
</Wrapper>
);
}
@@ -145,8 +179,10 @@ export default function Signup() {
<Box color="gray.200" minH="100vh" w="full">
<TopNavbar />
<VStack mx="auto" py={24} maxW="2xl" align="start" fontSize="lg">
<Heading size="lg">🏆 Prompt Engineering World Championships</Heading>
<VStack mx="auto" py={24} maxW="2xl" px={4} align="center" fontSize="lg">
<Heading size="lg" textAlign="center">
🏆 Prompt Engineering World Championships
</Heading>
<CountdownTimer
date={new Date("2023-08-14T00:00:00Z")}
fontSize="2xl"
@@ -156,7 +192,7 @@ export default function Signup() {
<ApplicationStatus py={8} alignSelf="center" />
<Text fontSize="lg">
<Text fontSize="lg" textAlign="left">
Think you have what it takes to be the best? Compete with the world's top prompt
engineers and see where you rank!
</Text>
@@ -165,7 +201,14 @@ export default function Signup() {
Event Details
</Heading>
<Table variant="simple">
<Tbody>
<Tbody
sx={{
th: {
base: { px: 0 },
md: { px: 6 },
},
}}
>
<Tr>
<Th>Kickoff</Th>
<Td>August 14</Td>

View File

@@ -14,6 +14,7 @@ import superjson from "superjson";
import { ZodError } from "zod";
import { getServerAuthSession } from "~/server/auth";
import { prisma } from "~/server/db";
import { capturePath } from "~/utils/analytics/serverAnalytics";
/**
* 1. CONTEXT
@@ -112,7 +113,7 @@ export const createTRPCRouter = t.router;
export const publicProcedure = t.procedure;
/** Reusable middleware that enforces users are logged in before running the procedure. */
const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
const enforceUserIsAuthed = t.middleware(async ({ ctx, next, path }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
@@ -134,6 +135,8 @@ const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
"Protected routes must perform access control checks then explicitly invoke the `ctx.markAccessControlRun()` function to ensure we don't forget access control on a route.",
});
capturePath(ctx.session, path);
return resp;
});

View File

@@ -51,7 +51,7 @@ const requestUpdatedPromptFunction = async (
originalModelProvider.inputSchema,
null,
2,
)}\n\nDo not add any assistant messages. Do not add any extra fields to the schema. Try to keep temperature consistent.`,
)}\n\nDo not add any assistant messages.`,
},
{
role: "user",
@@ -66,9 +66,11 @@ const requestUpdatedPromptFunction = async (
if (newModel.provider !== originalModel.provider) {
messages.push({
role: "user",
content: `The old provider was ${originalModel.provider}. The new provider is ${
content: `As seen in the first argument to definePrompt, the old provider endpoint was "${
originalModel.provider
}". The new provider endpoint is "${
newModel.provider
}. Here is the schema for the new model:\n---\n${JSON.stringify(
}". Here is the schema for the new model:\n---\n${JSON.stringify(
modelProviders[newModel.provider].inputSchema,
null,
2,

View File

@@ -3,6 +3,14 @@ import { TRPCError } from "@trpc/server";
import { type TRPCContext } from "~/server/api/trpc";
import { prisma } from "~/server/db";
const isAdmin = async (userId: string) => {
const user = await prisma.user.findFirst({
where: { id: userId, role: "ADMIN" },
});
return !!user;
};
// No-op method for protected routes that really should be accessible to anyone.
export const requireNothing = (ctx: TRPCContext) => {
ctx.markAccessControlRun();
@@ -18,21 +26,24 @@ export const requireCanViewExperiment = async (experimentId: string, ctx: TRPCCo
};
export const canModifyExperiment = async (experimentId: string, userId: string) => {
const experiment = await prisma.experiment.findFirst({
where: {
id: experimentId,
organization: {
organizationUsers: {
some: {
role: { in: [OrganizationUserRole.ADMIN, OrganizationUserRole.MEMBER] },
userId,
const [adminUser, experiment] = await Promise.all([
isAdmin(userId),
prisma.experiment.findFirst({
where: {
id: experimentId,
organization: {
organizationUsers: {
some: {
role: { in: [OrganizationUserRole.ADMIN, OrganizationUserRole.MEMBER] },
userId,
},
},
},
},
},
});
}),
]);
return !!experiment;
return adminUser || !!experiment;
};
export const requireCanModifyExperiment = async (experimentId: string, ctx: TRPCContext) => {

View File

@@ -1,13 +0,0 @@
// Make sure we're in the browser
import posthog from "posthog-js";
import { env } from "~/env.mjs";
const enableAnalytics = typeof window !== "undefined";
if (enableAnalytics) {
if (env.NEXT_PUBLIC_POSTHOG_KEY) {
posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: "https://app.posthog.com",
});
}
}

View File

@@ -0,0 +1,31 @@
import { type Session } from "next-auth";
import { useSession } from "next-auth/react";
import { useEffect } from "react";
import posthog from "posthog-js";
import { env } from "~/env.mjs";
// Make sure we're in the browser
const enableBrowserAnalytics = typeof window !== "undefined";
if (env.NEXT_PUBLIC_POSTHOG_KEY && enableBrowserAnalytics) {
posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: `${env.NEXT_PUBLIC_HOST}/ingest`,
});
}
export const identifySession = (session: Session) => {
if (!session.user) return;
posthog.identify(session.user.id, {
name: session.user.name,
email: session.user.email,
});
};
export const SessionIdentifier = () => {
const session = useSession().data;
useEffect(() => {
if (session && enableBrowserAnalytics) identifySession(session);
}, [session]);
return null;
};

View File

@@ -0,0 +1,14 @@
import { type Session } from "next-auth";
import { PostHog } from "posthog-node";
import { env } from "~/env.mjs";
export const posthogServerClient = env.NEXT_PUBLIC_POSTHOG_KEY
? new PostHog(env.NEXT_PUBLIC_POSTHOG_KEY, {
host: "https://app.posthog.com",
})
: null;
export const capturePath = (session: Session, path: string) => {
if (!session.user || !posthogServerClient) return;
posthogServerClient?.capture({ distinctId: session.user.id, event: path });
};