diff --git a/control-plane/.env b/control-plane/.env new file mode 100644 index 00000000..5e631cb7 --- /dev/null +++ b/control-plane/.env @@ -0,0 +1,22 @@ +NODE_ENV="development" + +APP_ORIGIN="http://localhost:3001" + +DATABASE_SSL_DISABLED="true" +DATABASE_URL="postgresql://inferable:inferable@inferable: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 + diff --git a/control-plane/README.md b/control-plane/README.md index f3ff3b05..9a2577f8 100644 --- a/control-plane/README.md +++ b/control-plane/README.md @@ -21,6 +21,43 @@ 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`. This contains base variables for local development but you will need to provide the following: + +- 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`. + ## Documentation - [Inferable documentation](https://docs.inferable.ai/) contains all the information you need to get started with Inferable. diff --git a/control-plane/docker-compose.dev.yml b/control-plane/docker-compose.dev.yml index c94fc75f..0c07cc1e 100644 --- a/control-plane/docker-compose.dev.yml +++ b/control-plane/docker-compose.dev.yml @@ -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: @@ -43,4 +43,6 @@ configs: queues { run-process {} run-generate-name {} + customer-telemetry {} + external-tool-call {} } diff --git a/control-plane/src/index.ts b/control-plane/src/index.ts index 647b0b25..bb75537a 100644 --- a/control-plane/src/index.ts +++ b/control-plane/src/index.ts @@ -111,16 +111,15 @@ 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, + }); 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([ @@ -130,14 +129,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(), ] : []), ]) diff --git a/control-plane/src/modules/auth/auth.ts b/control-plane/src/modules/auth/auth.ts index 7f4a909a..77afb1ee 100644 --- a/control-plane/src/modules/auth/auth.ts +++ b/control-plane/src/modules/auth/auth.ts @@ -113,17 +113,17 @@ export const plugin = fastifyPlugin(async (fastify: FastifyInstance) => { export const extractAuthState = async ( token: string, ): Promise => { - // 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; }, @@ -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 () { diff --git a/control-plane/src/utilities/env.ts b/control-plane/src/utilities/env.ts index 299479bb..7358ad3d 100644 --- a/control-plane/src/utilities/env.ts +++ b/control-plane/src/utilities/env.ts @@ -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), @@ -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), @@ -75,11 +74,7 @@ const envSchema = z 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", @@ -89,11 +84,11 @@ const envSchema = z "ANALYTICS_BUCKET_NAME", ]; - if (value.MASTER_API_SECRET) { + if (value.MANAGEMENT_API_SECRET) { return ctx.addIssue({ code: z.ZodIssueCode.custom, - message: "MASTER_API_SECRET can not be set for EE Deployment", - path: ["MASTER_API_SECRET"], + message: "MANAGEMENT_API_SECRET can not be set for EE Deployment", + path: ["MANAGEMENT_API_SECRET"], }); }