Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Improve local development experience #168

Merged
merged 1 commit into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions control-plane/.env.base
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
NODE_ENV="development"

APP_ORIGIN="http://localhost:3001"

DATABASE_SSL_DISABLED="true"
DATABASE_URL="postgresql://inferable:inferable@localhost:5432/inferable"

REDIS_URL="redis://localhost:6379"

SQS_BASE_QUEUE_URL='http://localhost:9324/000000000000'
SQS_RUN_PROCESS_QUEUE_URL='http://localhost:9324/000000000000/run-process'
SQS_RUN_GENERATE_NAME_QUEUE_URL='http://localhost:9324/000000000000/run-generate-name'
SQS_CUSTOMER_TELEMETRY_QUEUE_URL='http://localhost:9324/000000000000/customer-telemetry'
SQS_EXTERNAL_TOOL_CALL_QUEUE_URL='http://localhost:9324/000000000000/external-tool-call'

# JWKS_URL=
# MANAGEMENT_API_SECRET=

# ANTHROPIC_API_KEY=
# COHERE_API_KEY=
# BEDROCK_AVAILABLE=
1 change: 1 addition & 0 deletions control-plane/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ENV SHORT_VERSION=$SHORT_VERSION
COPY --from=build /build/ /app

ENV NODE_ENV=production
ENV ENVIRONMENT=prod

EXPOSE 4000
CMD [ "npm", "run", "start" ]
58 changes: 58 additions & 0 deletions control-plane/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,64 @@ Inferable's control-plane is open-source and self-hostable.

[Self hosting guide](https://docs.inferable.ai/pages/self-hosting)

### Local Development

To run the control plane locally for development:

1. Start the local resources required for development:
```bash
docker compose -f docker-compose.dev.yml up
```

This will start:
- PostgreSQL database with pgvector
- Redis for caching
- ElasticMQ for a local SQS-compatible queue implementation

2. Populate environment variables:

Development environment varaiables are managed in the `.env`.

`.env.base` contains a base set of required environment variables. Copy `.env.base` to `.env`.

```base
cp .env.base .env
```

You will need to populate the following environment variables in `.env`:

- Model provider API keys (`ANTHROPIC_API_KEY` and `COHERE_API_KEY`) OR `BEDROCK_AVAILABLE`
- If you specify `BEDROCK_AVAILABLE` ensure your environment has access to AWS Bedrock. (See [routing.ts](https://github.com/inferablehq/inferable/blob/main/control-plane/src/modules/models/routing.ts) for model requirements)
- `JWKS_URL` OR `MANAGEMENT_API_SECRET` (For headless mode)

4. Run DB migrations:

Inferable uses [drizzle](https://github.com/drizzle-team/drizzle-orm) to manage database migrations. To run migrations:

```bash
npm run migrate
```

3. Start the control plane:
```bash
npm run dev
```

The API will be available at `http://localhost:4000`.

4. Connect via the CLI (Optional):

```bash
npm install -g @inferable/cli
export INFERABLE_API_ENDPOINT=http://localhost:4000

# If running in headless mode, you will be prompted for the management API secret
inf auth login

# Create a new cluster
inf clusters create
```

## Documentation

- [Inferable documentation](https://docs.inferable.ai/) contains all the information you need to get started with Inferable.
Expand Down
8 changes: 5 additions & 3 deletions control-plane/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ services:
image: pgvector/pgvector:pg16
container_name: postgres
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
POSTGRES_DB: mydb
POSTGRES_USER: inferable
POSTGRES_PASSWORD: inferable
POSTGRES_DB: inferable
ports:
- "5432:5432"
volumes:
Expand Down Expand Up @@ -43,4 +43,6 @@ configs:
queues {
run-process {}
run-generate-name {}
customer-telemetry {}
external-tool-call {}
}
18 changes: 9 additions & 9 deletions control-plane/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,16 @@ app.addHook("onRequest", (request, _reply, done) => {
const startTime = Date.now();

(async function start() {
logger.info("Starting server");
logger.info("Starting server", {
environment: env.ENVIRONMENT,
ee: env.EE_DEPLOYMENT,
headless: !!env.MANAGEMENT_API_SECRET
});

if (env.ENVIRONMENT === "prod") {
await runMigrations().then(() => {
logger.info("Database migrated", { latency: Date.now() - startTime });
});
}
await runMigrations()

if (!env.EE_DEPLOYMENT) {
logger.info("Running in hobby mode");
logger.info("Database migrated", { latency: Date.now() - startTime });
}

await Promise.all([
Expand All @@ -130,14 +130,14 @@ const startTime = Date.now();
workflows.start(),
knowledge.start(),
models.start(),
redis.start(),
...(env.EE_DEPLOYMENT
? [
flagsmith?.getEnvironmentFlags(),
analytics.start(),
customerTelemetry.start(),
analytics.start(),
toolhouse.start(),
externalCalls.start(),
redis.start(),
]
: []),
])
Expand Down
16 changes: 8 additions & 8 deletions control-plane/src/modules/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,17 @@ export const plugin = fastifyPlugin(async (fastify: FastifyInstance) => {
export const extractAuthState = async (
token: string,
): Promise<Auth | undefined> => {
// Master Secret support (Hobby deployments only)
if (token && token === env.MASTER_API_SECRET) {
// Management Secret support (Hobby deployments only)
if (token && token === env.MANAGEMENT_API_SECRET) {
// This is also validated on startup
if (env.EE_DEPLOYMENT) {
throw new Error("Can not use master secret in EE deployment");
throw new Error("Can not use management secret in EE deployment");
}

return {
type: "api",
entityId: "MASTER_API_SECRET",
organizationId: "MASTER",
entityId: "MANAGEMENT_API_SECRET",
organizationId: "ROOT",
canAccess: async function () {
return this;
},
Expand All @@ -134,14 +134,14 @@ export const extractAuthState = async (
return this;
},
isMachine: function () {
throw new AuthenticationError("Master API secret auth is not machine");
throw new AuthenticationError("Management API secret auth is not machine");
},
isClerk: function () {
throw new AuthenticationError("Master API secret auth is not clerk");
throw new AuthenticationError("Management API secret auth is not clerk");
},
isCustomerProvided: function () {
throw new AuthenticationError(
"Master API secret auth is not customer provided",
"Management API secret auth is not customer provided",
);
},
isAdmin: function () {
Expand Down
43 changes: 24 additions & 19 deletions control-plane/src/utilities/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const envSchema = z
LOG_LEVEL: z.enum(["error", "warn", "info", "debug"]).default("info"),
ENABLE_FASTIFY_LOGGER: truthy.default(false),

MASTER_API_SECRET: z.string().optional(),
MANAGEMENT_API_SECRET: z.string().optional(),

DATABASE_URL: z.string().url(),
DATABASE_SSL_DISABLED: truthy.default(false),
Expand All @@ -36,25 +36,24 @@ const envSchema = z

JOB_LONG_POLLING_TIMEOUT: z.number().default(15),

REDIS_URL: z.string().url(),

ANTHROPIC_API_KEY: z.string().optional(),
COHERE_API_KEY: z.string().optional(),

SQS_RUN_PROCESS_QUEUE_URL: z.string(),
SQS_RUN_GENERATE_NAME_QUEUE_URL: z.string(),
SQS_LEARNING_INGEST_QUEUE_URL: z.string().optional(),
SQS_CUSTOMER_TELEMETRY_QUEUE_URL: z.string(),
SQS_EXTERNAL_TOOL_CALL_QUEUE_URL: z.string(),

SQS_BASE_QUEUE_URL: z.string().optional(),

// Required in EE (Disabled by default)
EE_DEPLOYMENT: truthy.default(false),

SQS_LEARNING_INGEST_QUEUE_URL: z.string().optional(),
SQS_CUSTOMER_TELEMETRY_QUEUE_URL: z.string().optional(),
SQS_EXTERNAL_TOOL_CALL_QUEUE_URL: z.string().optional(),

APP_ORIGIN: z.string().url().optional(),

REDIS_URL: z.string().url().optional(),

JWKS_URL: z.string().url().optional(),
JWT_IGNORE_EXPIRATION: truthy.default(false),

Expand All @@ -71,15 +70,29 @@ const envSchema = z
ANALYTICS_BUCKET_NAME: z.string().optional(),
})
.superRefine((value, ctx) => {
if (!value.MANAGEMENT_API_SECRET && !value.JWKS_URL) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "MANAGEMENT_API_SECRET or JWKS_URL is required",
path: ["MANAGEMENT_API_SECRET", "JWKS_URL"],
});
}

if (value.MANAGEMENT_API_SECRET) {
if (value.JWKS_URL) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "MANAGEMENT_API_SECRET can not be set with JWKS_URL (Headless mode only)",
path: ["MANAGEMENT_API_SECRET"],
});
}
}

if (!value.EE_DEPLOYMENT) {
return;
}
const EE_REQUIRED = [
"SQS_LEARNING_INGEST_QUEUE_URL",
"SQS_CUSTOMER_TELEMETRY_QUEUE_URL",
"SQS_EXTERNAL_TOOL_CALL_QUEUE_URL",
"APP_ORIGIN",
"REDIS_URL",
"JWKS_URL",
"HYPERDX_API_KEY",
"ROLLBAR_ACCESS_TOKEN",
Expand All @@ -89,14 +102,6 @@ const envSchema = z
"ANALYTICS_BUCKET_NAME",
];

if (value.MASTER_API_SECRET) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "MASTER_API_SECRET can not be set for EE Deployment",
path: ["MASTER_API_SECRET"],
});
}

for (const key of EE_REQUIRED) {
//eslint-disable-next-line @typescript-eslint/no-explicit-any
if (!(value as any)[key]) {
Expand Down