Getting Started
Synthetic was formerly named glhf.chat. You may still find references to glhf in the
codebase.
Start the backends
docker compose up --detach
First-time setup
Currently, the canonical keys are stored in AWS Secrets Manager. Someone will need to grant you access.
Then install the AWS CLI, and run:
# (One time only) Configure AWS SSO login.
aws configure sso
# SSO session name: synthetic
# SSO start URL: https://syntheticlab.awsapps.com/start
# SSO region: us-east-1
# SSO registration scopes: sso:account:access
# Default client Region: us-east-1
# CLI default output format: json
# Profile name: default
# Login to AWS
aws sso login
# Install packages
npm install
# Set up your .env file. You can run this again in the future if new secrets or
# environment variables get added.
npm run sync-dotenv
First-time setup: Migrations
npx tsx ./scripts/migrate.ts
First-time setup: Enable local HTTPS
# MacOS
brew install mkcert
# Linux & other OS:
# https://github.com/FiloSottile/mkcert?tab=readme-ov-file#linux
# Add mkcert local Root CA to local trusted CA store.
mkcert -install
# Generate and sign certificates
cd certs
mkcert localhost
If you are accessing the dev instance from a different URL, replace localhost with the correct
hostname and either rename your certs to localhost*.pem or edit server.ts.
Run the dev server
npm run dev:nobuild
# In another terminal session
# This enqueues jobs that run on a schedule. (eg. cleanup, usage credit auto-reload)
# - A more apt name would be "scheduler". Autoscaler is a holdover from when this job
# handled scaling on-demand GPUs up and down.
npx tsx autoscaler.ts
# In another terminal session
# This processes jobs from all queues (eg. the "chat" queue for responding to user messages)
npx tsx queue-worker.ts --work-all-queues
# In another terminal session
# This runs the python API which performs token counting via model tokenizers from HuggingFace.
# Setup: python-api/README.md
cd python-api
uv run fastapi dev --port 8080
Chat with LLMs
-
Open https://localhost:3000/ and create an account!
-
Ask for access to the Stripe development sandbox if you don't have access already: https://dashboard.stripe.com/acct_1QFT7V2NOm0u2wFs/test/settings.
-
(Optional) Set up the Stripe CLI to send webhooks to your development server. This will allow automatic syncing of Stripe state to your development server.
This is useful for prod-parity validation of the billing system, but for first time setup, this can be skipped.
Example invocation:
stripe listen --forward-to https://localhost:3000/billing/stripe/webhook --skip-verify -
Find your development user and create a subscription for your user in the Stripe sandbox.
-
If you did not set up the Stripe CLI above, visit https://localhost:3000/admin/subscriptions/sync and enter the subscription ID to sync your subscription from Stripe to your Next.js dev server.
-
Verify the Subscription is active in https://localhost:3000/billing
-
You're good to go!
Running a REPL
Use the npm run repl command, which will automatically open up a TypeScript
REPL for you with the correct env vars configured and tracing set up. This
works both in development, and SSHed into production machines if necessary.
Queues & workers
We have a small layer over Postgres and Redis for distributed job queues. Jobs
are stored in Postgres, and we occasionally poll it for new work; however, we
also attempt to instantly distribute work via Redis LPUSH/BLPOP, to avoid the
latency of polling. The basic abstraction is a Queue (imported from
@/app/queues), which takes:
- A name, and
- A mapping of job names to worker configurations
Worker configurations should generally be set up by first defining their max allowed attempts before failure, and then giving a Structural type for the job data and an async callback that runs the job. For example:
import { t } from "structural";
import { queue, maxAttempts } from "@/app/queues";
const worker = maxAttempts(5);
export const myQueue = queue("myQueue", {
myJob: worker(t.str, async (someString) => {
// ... business logic goes here
}),
});
// To push a job:
await myQueue.push("myJob", "hello world");
Then, import all your queue files in @/queue-worker.ts, which is the main
worker entrypoint. Your queue will automatically start running. Jobs will be
delivered in-order; however, since jobs can be worked across multiple machines,
there's no guarantee of them finishing in-order: only in-order delivery is
guaranteed.
If a job fails too many times, it will get added to its queue's corresponding dead-letter queue, which is a capped-size list in Redis for holding permanently-failed jobs for debugging. You can check your queue's dead-letter queue with:
const deadLetters = await myQueue.deadLetters();
The dead-letter queue's size is fixed, so if you have too many job failures, there's no guarantee you'll be able to see all of them. The dead-letter queue isn't a durability mechanism: it's only a debugging tool.
DevOps
Github Actions and Workflows
The .github directory contains actions and workflows for Synthetic DevOps.
Testing
Workflows can be tested locally without commits/pushes by using nektos/act
act push -s GITHUB_TOKEN="$(gh auth token)" --secret-file .env -v