From 8801f0189188943eced532c4c381c0e56a8e5731 Mon Sep 17 00:00:00 2001 From: John Smith Date: Tue, 3 Dec 2024 15:56:46 +1030 Subject: [PATCH 1/5] chore: Default call summarization to false (#208) --- control-plane/src/modules/contract.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control-plane/src/modules/contract.ts b/control-plane/src/modules/contract.ts index 24d5ae4a..7bb61faa 100644 --- a/control-plane/src/modules/contract.ts +++ b/control-plane/src/modules/contract.ts @@ -565,7 +565,7 @@ export const definition = { .describe("Enable reasoning traces"), callSummarization: z .boolean() - .default(true) + .default(false) .optional() .describe("Enable summarization of oversized call results"), interactive: z From 3441a37a3f347b8e507e8c7fa8bc801457963039 Mon Sep 17 00:00:00 2001 From: John Smith Date: Tue, 3 Dec 2024 16:55:02 +1030 Subject: [PATCH 2/5] docs: Initial pg-stat-analysis project (#210) * docs: Initial pg-stat-analysis project * Update index.ts Co-authored-by: Nadeesha Cabral --------- Co-authored-by: Nadeesha Cabral --- data-connector/README.md | 2 +- examples/pg-stat-analysis/.gitignore | 1 + examples/pg-stat-analysis/README.md | 54 ++ examples/pg-stat-analysis/package-lock.json | 707 ++++++++++++++++++++ examples/pg-stat-analysis/package.json | 19 + examples/pg-stat-analysis/src/index.ts | 69 ++ 6 files changed, 851 insertions(+), 1 deletion(-) create mode 100644 examples/pg-stat-analysis/.gitignore create mode 100644 examples/pg-stat-analysis/README.md create mode 100644 examples/pg-stat-analysis/package-lock.json create mode 100644 examples/pg-stat-analysis/package.json create mode 100644 examples/pg-stat-analysis/src/index.ts diff --git a/data-connector/README.md b/data-connector/README.md index 75344c89..924c6116 100644 --- a/data-connector/README.md +++ b/data-connector/README.md @@ -46,7 +46,7 @@ docker run -e INFERABLE_API_SECRET="sk_xxxx" \ 2. Rename `.env.example` to `.env` and set your Inferable API secret: ```bash -INFERABLE_SECRET=your_secret_here +INFERABLE_API_SECRET=your_secret_here # DEMO POSTGRES CONFIG POSTGRES_URL=postgresql://postgres:postgres@db:5432/postgres diff --git a/examples/pg-stat-analysis/.gitignore b/examples/pg-stat-analysis/.gitignore new file mode 100644 index 00000000..15b1ad93 --- /dev/null +++ b/examples/pg-stat-analysis/.gitignore @@ -0,0 +1 @@ +history.json diff --git a/examples/pg-stat-analysis/README.md b/examples/pg-stat-analysis/README.md new file mode 100644 index 00000000..996e2143 --- /dev/null +++ b/examples/pg-stat-analysis/README.md @@ -0,0 +1,54 @@ +

+ +

+ +# @inferable/pg-stat-analysis + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +## Overview + +`@inferable/pg-stat-analysis` is an analytical agent designed to evaluate and improve the performance of your PostgreSQL database through recommendations based on access patterns. The agent provides suggestions such as creating indexes or other optimization strategies to enhance query performance. It makes use of the `pg_stat_statements` extension to provide data-driven recommendations. + +## Prerequisites + +- **Node.js** - Ensure you have Node.js installed. +- **PostgreSQL** - A running PostgreSQL instance with the `pg_stat_statements` [extension enabled](https://www.postgresql.org/docs/current/pgstatstatements.html). +- **Data Connector** - This projects assumes you are runnign the [Inferable Data Connector](https://github.com/inferablehq/inferable/tree/main/data-connector) which provides the connection to your database. + +## Installation + +1. **Install Dependencies:** + + ```bash + npm install + ``` + +2. **Environment Setup:** + + Create a `.env` file in the project root with the following variable: + + ``` + INFERABLE_API_SECRET=your_inferable_api_secret + ``` + + Replace `your_inferable_api_secret` with your Cluster's [API secret](https://docs.inferable.ai/pages/auth#cluster-api-keys). + +## Usage + +Once the environment is set up, you can run the project in development mode using the following command: + +```bash +npm run dev +``` + +This will initiate the agent, which will analyze your database and provide performance-enhancing recommendations stored in `history.json`. +Multiple runs will append to the existing data in `history.json`. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## Support + +If you have any questions or issues, please open an issue on this repository. diff --git a/examples/pg-stat-analysis/package-lock.json b/examples/pg-stat-analysis/package-lock.json new file mode 100644 index 00000000..dbc25f79 --- /dev/null +++ b/examples/pg-stat-analysis/package-lock.json @@ -0,0 +1,707 @@ +{ + "name": "@inferable/pg-stat-analysis", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@inferable/pg-stat-analysis", + "license": "MIT", + "dependencies": { + "inferable": "^0.30.58", + "tsx": "^4.19.2", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/node": "^22.10.1", + "dotenv": "^16.4.6" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-rest/core": { + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/@ts-rest/core/-/core-3.51.0.tgz", + "integrity": "sha512-v6lnWEcpZj1UgN9wb84XQ+EORP1QEtncFumoXMJjno5ZUV6vdjKze3MYcQN0C6vjBpIJPQEaI/gab2jr4/0KzQ==", + "license": "MIT", + "peerDependencies": { + "@types/node": "^18.18.7 || >=20.8.4", + "zod": "^3.22.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dotenv": { + "version": "16.4.6", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.6.tgz", + "integrity": "sha512-JhcR/+KIjkkjiU8yEpaB/USlzVi3i5whwOjpIRNGi9svKEXZSe+Qp6IWAjFjv+2GViAoDRCUv/QLNziQxsLqDg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/inferable": { + "version": "0.30.58", + "resolved": "https://registry.npmjs.org/inferable/-/inferable-0.30.58.tgz", + "integrity": "sha512-3PZSGTwFyPEYkqDht5gXBfC+gOgxK0a2sL9KTqGb7d6ujk+J0X/jAgz0ikF+SUK7INTsrc02mmYUV9S8eIUixg==", + "license": "MIT", + "dependencies": { + "@ts-rest/core": "^3.28.0", + "@types/debug": "^4.1.8", + "@types/json-schema": "^7.0.15", + "ajv": "=8.17.1", + "ajv-formats": "=3.0.1", + "debug": "^4.3.4", + "node-machine-id": "^1.1.12", + "prettier": "^3.3.3", + "zod": "^3.23.5", + "zod-to-json-schema": "^3.23.5" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", + "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz", + "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.23.3" + } + } + } +} diff --git a/examples/pg-stat-analysis/package.json b/examples/pg-stat-analysis/package.json new file mode 100644 index 00000000..aa939ded --- /dev/null +++ b/examples/pg-stat-analysis/package.json @@ -0,0 +1,19 @@ +{ + "name": "@inferable/pg-stat-analysis", + "description": "Inferable agent for analysing Postgres DB performance", + "private": true, + "author": "Inferable, Inc.", + "license": "MIT", + "scripts": { + "dev": "tsx -r dotenv/config src/index.ts" + }, + "dependencies": { + "inferable": "^0.30.58", + "tsx": "^4.19.2", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/node": "^22.10.1", + "dotenv": "^16.4.6" + } +} diff --git a/examples/pg-stat-analysis/src/index.ts b/examples/pg-stat-analysis/src/index.ts new file mode 100644 index 00000000..e30fac77 --- /dev/null +++ b/examples/pg-stat-analysis/src/index.ts @@ -0,0 +1,69 @@ +import { readFileSync, writeFileSync, existsSync } from 'fs'; +import { Inferable } from 'inferable'; +import { z } from 'zod'; + +const HISTORY_PATH = path.join(__dirname, 'history.json') + +if (!existsSync(HISTORY_PATH)) { + writeFileSync(HISTORY_PATH, JSON.stringify({ suggestions: [] })); +} + +const history = JSON.parse(readFileSync(HISTORY_PATH, 'utf-8')); + +const client = new Inferable() + +const resultSchema = z.object({ + summary: z.string(), + suggestions: z.array( + z.object({ + name: z.string(), + explanation: z.string().describe("A short explanation of the suggestion"), + impactedQueries: z.array(z.string()).describe("The SQL queries that will be impacted by the suggestion. Only include queries observed in pg_stat_statements, etc"), + remediation: z.string().describe("The SQL code to implement the suggestion"), + impact: z.number().describe("The estimated impact of the suggestion on query performance").min(0).max(10) + }) + ), +}) + +client.run({ + initialPrompt: ` +Evaluate the access patterns within this database and recomend possible suggestions to improve performance. +This could include creating indexes or other optimizations that will improve performance. + +If a query fails, evaluate the error message and try again if possible. + +Do not return without evaluating "pg_stat_statements" if the module is enabled. +Do not recomend VACUUM related suggestions if auto-vacuum is enabled. +Do not "infer" possible queries, evaluate them based on "pg_stat_statements" table. + +Only return suggestions that haven't been recomended before, it is ok to return no suggestions. +`, + systemPrompt: ` +You are a Postgres DB analysis agent. You have access to a Postgres database and your job is to answer questions about the data in the database. + +You have access to run queries against all 'pg_stat_*' tables as necessary to answer your questions. +`, + model: "claude-3-5-sonnet", + resultSchema, + input: history, +}) + .then(run => run.poll()) + .then(runData => { + const result = runData?.result + if (!result) { + console.log("No result") + return + } + + const resultData = resultSchema.parse(result) + + writeFileSync(HISTORY_PATH, JSON.stringify({ + suggestions: [ + ...history.suggestions, + ...resultData.suggestions.map(suggestion => ({ + name: suggestion.name, + remediation: suggestion.remediation + })), + ] + })); + }) From 0dbf6fc696b7234ea9e9d613e43114ee04493bcc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:12:08 +1030 Subject: [PATCH 3/5] chore(deps): Bump Inferable from 0.0.17 to 0.0.18 in /bootstrap-dotnet (#196) Bumps [Inferable](https://github.com/inferablehq/inferable) from 0.0.17 to 0.0.18. - [Commits](https://github.com/inferablehq/inferable/commits) --- updated-dependencies: - dependency-name: Inferable dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- bootstrap-dotnet/bootstrap-dotnet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap-dotnet/bootstrap-dotnet.csproj b/bootstrap-dotnet/bootstrap-dotnet.csproj index 5519a0a1..a43be10b 100644 --- a/bootstrap-dotnet/bootstrap-dotnet.csproj +++ b/bootstrap-dotnet/bootstrap-dotnet.csproj @@ -10,7 +10,7 @@ - + From a76eda1fb4b54b962554cc20097bd9bace3b96d8 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:42:57 +0000 Subject: [PATCH 4/5] [skip ci] Update bootstrap project archives --- archives/bootstrap-dotnet.zip | Bin 10430 -> 10430 bytes archives/bootstrap-go.zip | Bin 4641448 -> 4641448 bytes archives/bootstrap-node.zip | Bin 10031 -> 10031 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/archives/bootstrap-dotnet.zip b/archives/bootstrap-dotnet.zip index a82780f1984e2a758b352bf7d1836c43960ea5ca..36d229fccba166deadff6d9a2520b8e264a408f7 100644 GIT binary patch delta 800 zcmdlNxG#`5z?+$civa{;T$?BIskAo}UoFx(5On2Im#gQL3whefYEziDrS{K$_RVyumxtNeQ=ewr$A3QV zXC76@5+|FT~jw&X)X9^zU1K} zJ8rFI(FfGex>ckPk2GI#xW;wF ztCah2iqW3TId>ap_TTzc*Pz_`^w6t&tlj_S-1&Fj`%~R^i(0i;e;GibD*_B#4p8V$ z4rO;d|dGO+3d!( zmkBJ$#;XfcxY>$#2^(1Op|CYvP+8396f;O9!VoM16x}$%k(C1|6nV#r zY4zlrtd=l|$&zf+^*eFod z_|M1t%H3)k9R3Gv<*dwW2@I2+_$<>=R_V;=8v8OOUbgf8ZoIQBAKfT(uuUkucHokJ zNAt~EA(1;*8v5J57X&R!6)NyOY!UIvb1r}E@zZ<~Cq8yGbI*7cxxRk4o5_zP>B!sN zS&iy%<229A-Fr2LLHq96MGt?i4SLbm(Y*hwVZsIvi%mQW=d<4{y!c7#TLBAmP{VxB zi@c&LPY&K>T=r@5kM#*Ti&LE5Ww$*?jg~Q20*f=P*YSGo9ST5ds#!$zcQ*M?}SBeNI=n(rV5LU}bz<@c7y6 z#y~bGGM;4vOSDHxluU|ky@TByTc^F3IkLEQTT^dT%mk1 zPIzjZtflIJqN+zV7=_QM2Dc7uYLc2Yiu`^xPZYkEI-Eawl6nGI&sTL+JHj>Wzmr>iw1F4oD=6oqqrcN#6@vQTo%ouMYM`G(Jnefr|1$_M7M~EtD;Briff`z^os#; zT?~pFA}(%Zi_K-N8A>$w}(K#!96jV?(9#C}YE=@;PIpq4@3F yR_DjFHsunZX8*tcC0q1{KmQ@$J(;sf*S(svc$nnmd#)(kN#f8(T+j1E>-+=i`wKq+ delta 648 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 ICgJ4xADh1uhyVZp delta 4254 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 From 523d21c26ed6dc711e9fcf24522315fb3136e7ab Mon Sep 17 00:00:00 2001 From: Nadeesha Cabral Date: Tue, 3 Dec 2024 21:54:53 +1100 Subject: [PATCH 5/5] feat: Add graphql to data-connector (#209) * update * update * fix: Add graphql to data-connector * update * update --- data-connector/.env.example | 9 +- data-connector/README.md | 19 +- data-connector/config.json | 8 + data-connector/package-lock.json | 36 +- data-connector/package.json | 8 +- data-connector/src/graphql/graphql.ts | 315 ++++++++++++++++++ data-connector/src/index.ts | 44 ++- data-connector/src/{ => open-api}/open-api.ts | 2 +- data-connector/src/{ => postgres}/postgres.ts | 9 +- 9 files changed, 418 insertions(+), 32 deletions(-) create mode 100644 data-connector/src/graphql/graphql.ts rename data-connector/src/{ => open-api}/open-api.ts (99%) rename data-connector/src/{ => postgres}/postgres.ts (94%) diff --git a/data-connector/.env.example b/data-connector/.env.example index ba8237b0..25f50972 100644 --- a/data-connector/.env.example +++ b/data-connector/.env.example @@ -14,4 +14,11 @@ POSTGRES_SCHEMA=public # # OPENAPI_SPEC_URL=https://api.inferable.ai/public/oas.json # SERVER_URL=https://api.inferable.ai -# SERVER_AUTH_HEADER=Authorization: Bearer your_api_key_here +# SERVER_AUTH_HEADER=Bearer your_api_key_here\ + +# +# GraphQL config example. +# +# GRAPHQL_SCHEMA_URL=https://docs.github.com/public/fpt/schema.docs.graphql +# GRAPHQL_ENDPOINT=https://api.github.com/graphql +# GRAPHQL_AUTH_HEADER="bearer ghp_xxx" diff --git a/data-connector/README.md b/data-connector/README.md index 924c6116..de86594f 100644 --- a/data-connector/README.md +++ b/data-connector/README.md @@ -13,11 +13,11 @@ Inferable Data Connector is a bridge between your data systems and Inferable. Co ## Connectors -- [x] [Postgres](./src/postgres.ts) -- [x] [OpenAPI](./src/open-api.ts) -- [ ] [GraphQL](./src/graphql.ts) -- [ ] [MySQL](./src/mysql.ts) -- [ ] [SQLite](./src/sqlite.ts) +- [x] [Postgres](./src/postgres/postgres.ts) +- [x] [OpenAPI](./src/open-api/open-api.ts) +- [x] [GraphQL](./src/graphql/graphql.ts) +- [ ] [MySQL](./src/mysql/mysql.ts) +- [ ] [SQLite](./src/sqlite/sqlite.ts) ## Quick Start @@ -114,6 +114,15 @@ Each connector is defined in the `config.connectors` array. +
+GraphQL Connector Configuration + +- `config.connectors[].schemaUrl`: The URL to your GraphQL schema. Must be publicly accessible. +- `config.connectors[].endpoint`: The endpoint to use. (e.g. `https://api.inferable.ai`) +- `config.connectors[].defaultHeaders`: The default headers to use. (e.g. `{"Authorization": "Bearer "}`) + +
+ ### config.privacyMode When enabled (`config.privacyMode=1`), raw data is never sent to the model. Instead: diff --git a/data-connector/config.json b/data-connector/config.json index 5720f380..16f158d0 100644 --- a/data-connector/config.json +++ b/data-connector/config.json @@ -15,6 +15,14 @@ "defaultHeaders": { "Authorization": "process.env.SERVER_AUTH_HEADER" } + }, + { + "type": "graphql", + "schemaUrl": "process.env.GRAPHQL_SCHEMA_URL", + "endpoint": "process.env.GRAPHQL_ENDPOINT", + "defaultHeaders": { + "Authorization": "process.env.GRAPHQL_AUTH_HEADER" + } } ] } diff --git a/data-connector/package-lock.json b/data-connector/package-lock.json index 107a4e6e..de4dc235 100644 --- a/data-connector/package-lock.json +++ b/data-connector/package-lock.json @@ -1,15 +1,17 @@ { - "name": "inferable-sql-manager", + "name": "inferable-data-connector", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "inferable-sql-manager", + "name": "inferable-data-connector", "version": "1.0.0", "dependencies": { "dotenv": "^16.4.5", "fastify": "^4.28.1", + "graphql": "^16.9.0", + "graphql-tag": "^2.12.6", "inferable": "^0.30.48", "pg": "^8.13.1", "tsx": "^4.19.2" @@ -818,6 +820,30 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/graphql": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-tag": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, "node_modules/inferable": { "version": "0.30.48", "resolved": "https://registry.npmjs.org/inferable/-/inferable-0.30.48.tgz", @@ -1233,6 +1259,12 @@ "node": ">=12" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tsx": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", diff --git a/data-connector/package.json b/data-connector/package.json index c8fac5b4..38afdbff 100644 --- a/data-connector/package.json +++ b/data-connector/package.json @@ -12,13 +12,15 @@ "dependencies": { "dotenv": "^16.4.5", "fastify": "^4.28.1", + "graphql": "^16.9.0", + "graphql-tag": "^2.12.6", "inferable": "^0.30.48", "pg": "^8.13.1", "tsx": "^4.19.2" }, "devDependencies": { + "@faker-js/faker": "^9.1.0", "@types/node": "^20.11.19", - "typescript": "^5.3.3", - "@faker-js/faker": "^9.1.0" + "typescript": "^5.3.3" } -} \ No newline at end of file +} diff --git a/data-connector/src/graphql/graphql.ts b/data-connector/src/graphql/graphql.ts new file mode 100644 index 00000000..e9de219f --- /dev/null +++ b/data-connector/src/graphql/graphql.ts @@ -0,0 +1,315 @@ +import crypto from "crypto"; +import { + buildSchema, + GraphQLSchema, + GraphQLType, + isInputObjectType, + isListType, + isNonNullType, + isObjectType, +} from "graphql"; +import { approvalRequest, blob, ContextInput, Inferable } from "inferable"; +import { FunctionRegistrationInput } from "inferable/bin/types"; +import { z } from "zod"; +import type { DataConnector } from "../types"; + +export class GraphQLClient implements DataConnector { + private schema: GraphQLSchema | null = null; + private initialized = false; + private operations: ReturnType< + typeof this.graphqlOperationToInferableFunction + >[] = []; + + constructor( + private params: { + name?: string; + schemaUrl: string; + endpoint: string; + defaultHeaders?: Record; + privacyMode: boolean; + paranoidMode: boolean; + }, + ) {} + + public initialize = async () => { + try { + const response = await fetch(this.params.schemaUrl); + const schemaSDL = await response.text(); + this.schema = buildSchema(schemaSDL); + console.log( + `GraphQL schema loaded successfully from ${this.params.schemaUrl}`, + ); + + // Parse the schema and extract operations + const queryType = this.schema.getQueryType(); + const mutationType = this.schema.getMutationType(); + + if (queryType) { + const fields = queryType.getFields(); + for (const [fieldName, field] of Object.entries(fields)) { + const operation = this.graphqlOperationToInferableFunction( + fieldName, + field, + "query", + ); + this.operations.push(operation); + } + } + + if (mutationType) { + const fields = mutationType.getFields(); + for (const [fieldName, field] of Object.entries(fields)) { + const operation = this.graphqlOperationToInferableFunction( + fieldName, + field, + "mutation", + ); + this.operations.push(operation); + } + } + + console.log( + `Loaded ${this.operations.length} operations from GraphQL schema`, + ); + + if (this.params.privacyMode) { + console.log( + "Privacy mode is enabled, response data will not be sent to the model.", + ); + } + + this.initialized = true; + } catch (error) { + console.error("Failed to initialize GraphQL connection:", error); + throw error; + } + }; + + private graphqlOperationToInferableFunction = ( + fieldName: string, + field: any, + operationType: "query" | "mutation", + ): FunctionRegistrationInput => { + const args = field.args || []; + const hasArguments = args.length > 0; + + const operationDefinition = `${operationType} ${fieldName}${ + hasArguments + ? "($args: " + + field.args.map((arg: any) => `${arg.name}: ${arg.type}`).join(", ") + + ")" + : "" + } { + ${fieldName}${hasArguments ? "(args: $args)" : ""} + }`; + + return { + name: `${operationType}${fieldName}`, + description: `GraphQL ${operationType} operation: ${operationDefinition} +To understand the input and output types for this operation, use the searchGraphQLDefinition function with: +{ + "operation": "${operationType}", + "fieldName": "${fieldName}" +}`, + func: this.executeRequest, + schema: { + input: z.object({ + query: z + .string() + .describe( + `The full graphql ${operationType} to execute: ${operationDefinition} ${fieldName} ${hasArguments ? `($args)` : ``} { ... }`, + ), + variables: hasArguments + ? z + .record(z.any()) + .describe( + `Operation variables. Use searchGraphQLDefinition to see the required types.`, + ) + : z.undefined(), + }), + }, + }; + }; + + executeRequest = async ( + input: { + query: string; + variables?: Record; + }, + ctx: ContextInput, + ) => { + if (this.params.paranoidMode) { + if (!ctx.approved) { + console.log("Request requires approval"); + return approvalRequest(); + } else { + console.log("Request approved"); + } + } + + if (!this.initialized) throw new Error("GraphQL schema not initialized"); + if (!this.schema) throw new Error("GraphQL schema not initialized"); + + // Merge default headers with the Content-Type header + const headers = { + "Content-Type": "application/json", + ...this.params.defaultHeaders, + }; + + const response = await fetch(this.params.endpoint, { + method: "POST", + headers, + body: JSON.stringify(input), + }); + + const data = await response.json(); + + if (this.params.privacyMode) { + return { + message: + "This request was executed in privacy mode. Data was returned to the user directly.", + blob: blob({ + name: "Results", + type: "application/json", + data, + }), + }; + } + + return data; + }; + + private connectionStringHash = () => { + return crypto + .createHash("sha256") + .update(this.params.schemaUrl) + .digest("hex") + .substring(0, 8); + }; + + createService = (client: Inferable) => { + const service = client.service({ + name: this.params.name ?? `graphql${this.connectionStringHash()}`, + }); + + // Register the search function first + service.register(this.searchGraphQLDefinitionToInferableFunction()); + + // Then register all the operations + this.operations.forEach((operation) => { + service.register(operation); + }); + + return service; + }; + + private searchGraphQLDefinitionToInferableFunction = + (): FunctionRegistrationInput => { + return { + name: "searchGraphQLDefinition", + description: + "Search for input and output type definitions of a GraphQL operation", + func: async (input: { + operation: "query" | "mutation"; + fieldName: string; + }) => { + if (!this.schema) throw new Error("GraphQL schema not initialized"); + + const operationType = + input.operation === "query" + ? this.schema.getQueryType() + : this.schema.getMutationType(); + + if (!operationType) { + throw new Error(`No ${input.operation} type found in schema`); + } + + const field = operationType.getFields()[input.fieldName]; + if (!field) { + throw new Error( + `No field ${input.fieldName} found in ${input.operation} type`, + ); + } + + const args = field.args || []; + const argTypes: Record = {}; + + args.forEach((arg) => { + argTypes[arg.name] = { + type: this.getTypeString(arg.type), + definition: this.getTypeDefinition(arg.type), + }; + }); + + const returnType = { + type: this.getTypeString(field.type), + definition: this.getTypeDefinition(field.type), + }; + + return { + operation: input.operation, + fieldName: input.fieldName, + inputTypes: argTypes, + outputType: returnType, + }; + }, + schema: { + input: z.object({ + operation: z.enum(["query", "mutation"]), + fieldName: z.string(), + }), + }, + }; + }; + + private getTypeString = (type: GraphQLType): string => { + if (isNonNullType(type)) { + return `${this.getTypeString(type.ofType)}!`; + } + if (isListType(type)) { + return `[${this.getTypeString(type.ofType)}]`; + } + return type.toString(); + }; + + private getTypeDefinition = (type: GraphQLType): any => { + if (isNonNullType(type)) { + return this.getTypeDefinition(type.ofType); + } + if (isListType(type)) { + return { + type: "list", + ofType: this.getTypeDefinition(type.ofType), + }; + } + if (isObjectType(type) || isInputObjectType(type)) { + const fields = type.getFields(); + const fieldDefs: Record = {}; + + Object.entries(fields).forEach(([fieldName, field]) => { + fieldDefs[fieldName] = { + type: this.getTypeString(field.type), + description: field.description || undefined, + }; + + if ("args" in field && field.args.length > 0) { + fieldDefs[fieldName].args = field.args.map((arg) => ({ + name: arg.name, + type: this.getTypeString(arg.type), + description: arg.description || undefined, + })); + } + }); + + return { + type: "object", + fields: fieldDefs, + }; + } + + return { + type: "scalar", + name: type.toString(), + }; + }; +} diff --git a/data-connector/src/index.ts b/data-connector/src/index.ts index 314a8dd7..2aabddfd 100644 --- a/data-connector/src/index.ts +++ b/data-connector/src/index.ts @@ -1,32 +1,33 @@ import "dotenv/config"; import { Inferable } from "inferable"; -import { PostgresClient } from "./postgres"; +import { PostgresClient } from "./postgres/postgres"; import { RegisteredService } from "inferable/bin/types"; -import { OpenAPIClient } from "./open-api"; +import { OpenAPIClient } from "./open-api/open-api"; +import { GraphQLClient } from "./graphql/graphql"; -const parseConfig = () => { - const config = require("../config.json"); - - config.connectors.forEach((connector: any) => { - for (const [key, value] of Object.entries(connector)) { - if (typeof value === "string" && value.startsWith("process.env.")) { - const actual = process.env[value.replace("process.env.", "")]; - if (!actual) { - throw new Error(`Environment variable ${value} not found`); - } - connector[key] = actual; +const parseConfig = (connector: any) => { + for (const [key, value] of Object.entries(connector)) { + if (typeof value === "object") { + console.log("Parsing object", key); + connector[key] = parseConfig(value); + } else if (typeof value === "string" && value.startsWith("process.env.")) { + const actual = process.env[value.replace("process.env.", "")]; + if (!actual) { + throw new Error(`Environment variable ${value} not found`); } + connector[key] = actual; } - }); + } - return config; + return connector; }; (async function main() { const client = new Inferable(); - const config = parseConfig(); + const config = require("../config.json"); + config.connectors = parseConfig(config.connectors); if (config.connectors.length === 0) { throw new Error("No connectors found in config.json"); @@ -54,6 +55,17 @@ const parseConfig = () => { await openAPIClient.initialize(); const service = openAPIClient.createService(client); services.push(service); + } else if (connector.type === "graphql") { + const graphQLClient = new GraphQLClient({ + ...connector, + paranoidMode: config.paranoidMode === 1, + privacyMode: config.privacyMode === 1, + }); + await graphQLClient.initialize(); + const service = graphQLClient.createService(client); + services.push(service); + } else { + throw new Error(`Unknown connector type: ${connector.type}`); } } diff --git a/data-connector/src/open-api.ts b/data-connector/src/open-api/open-api.ts similarity index 99% rename from data-connector/src/open-api.ts rename to data-connector/src/open-api/open-api.ts index 07d850f2..bda5852b 100644 --- a/data-connector/src/open-api.ts +++ b/data-connector/src/open-api/open-api.ts @@ -1,7 +1,7 @@ import { approvalRequest, blob, ContextInput, Inferable } from "inferable"; import { z } from "zod"; import fetch from "node-fetch"; -import type { DataConnector } from "./types"; +import type { DataConnector } from "../types"; import { OpenAPIV3 } from "openapi-types"; import crypto from "crypto"; import { FunctionRegistrationInput } from "inferable/bin/types"; diff --git a/data-connector/src/postgres.ts b/data-connector/src/postgres/postgres.ts similarity index 94% rename from data-connector/src/postgres.ts rename to data-connector/src/postgres/postgres.ts index 4fdbc44b..b7bc33c2 100644 --- a/data-connector/src/postgres.ts +++ b/data-connector/src/postgres/postgres.ts @@ -3,11 +3,11 @@ import { approvalRequest, blob, ContextInput, Inferable } from "inferable"; import pg from "pg"; import { z } from "zod"; import crypto from "crypto"; -import type { DataConnector } from "./types"; +import type { DataConnector } from "../types"; export class PostgresClient implements DataConnector { private client: pg.Client | null = null; - private initialized: Promise; + private initialized = false; constructor( private params: { @@ -34,6 +34,7 @@ export class PostgresClient implements DataConnector { process.removeListener("SIGTERM", this.handleSigterm); process.on("SIGTERM", this.handleSigterm); + this.initialized = true; } catch (error) { console.error("Failed to initialize database connection:", error); throw error; @@ -71,7 +72,7 @@ export class PostgresClient implements DataConnector { }; getContext = async () => { - await this.initialized; + if (!this.initialized) throw new Error("Database not initialized"); const client = await this.getClient(); const tables = await this.getAllTables(); @@ -118,7 +119,7 @@ export class PostgresClient implements DataConnector { } } - await this.initialized; + if (!this.initialized) throw new Error("Database not initialized"); const client = await this.getClient(); const res = await client.query(input.query);