mirror of
https://github.com/run-llama/create-llama.git
synced 2026-07-02 19:14:28 -04:00
Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05748bdf10 | |||
| d60b3c5a96 | |||
| c3e9ed3df4 | |||
| 1fde1dc585 | |||
| cd50a33d43 | |||
| ed114856d9 | |||
| 69c2e16c82 | |||
| f5da6623cf | |||
| 0950cb90f2 | |||
| bb53425b4b | |||
| bbd5b8ddd6 | |||
| 260d37a3f1 | |||
| 7873bfb030 | |||
| 0c7c41ee3b | |||
| 56537a1473 | |||
| d8dfc29edd | |||
| 84db798353 | |||
| 67a062af14 | |||
| 0bc8e75c64 | |||
| 6bd5e7b77a | |||
| 38bc1d1350 | |||
| cb1001de95 | |||
| 78776ac51e | |||
| 416073db1d | |||
| 84929de8b2 | |||
| 6fe240b854 | |||
| 8bb1024d0f | |||
| 988bfc2a60 | |||
| 056e376ee0 | |||
| 819cccb11a | |||
| 8a5ece10c2 | |||
| 63bb0505d6 | |||
| 2e80ef47ee | |||
| a1feb524e9 | |||
| 06823da849 | |||
| 7bd3ed551f | |||
| c981eb1423 | |||
| c094b0c6bf | |||
| e2567ffc03 | |||
| 5d8d752b16 | |||
| a0b04be23c | |||
| 94a2809ecd | |||
| e29ef92564 | |||
| 6bdd4ac69d | |||
| 1ad25451a6 | |||
| cfb5257a1e | |||
| 046ff06157 | |||
| 8b81b17984 | |||
| f1c3e8df69 | |||
| 089916a148 | |||
| 3bb94da804 | |||
| 418bf9ba8a | |||
| e5d20b66f6 | |||
| ae7b30106d | |||
| 5fb64b74ca | |||
| e4665b6c0d | |||
| 5463d3bf4b | |||
| 7225e916fd | |||
| 897feb9914 | |||
| 66b5f38eda | |||
| 1c3d0b19ec | |||
| a0dec80d88 | |||
| 1be69a5aa4 | |||
| 6acccd2b04 | |||
| 753229dfae | |||
| 9efcffe112 | |||
| 0cf6386e17 | |||
| a6e76e1cf3 | |||
| 2aaf99e256 | |||
| 1d78202ef4 | |||
| ee2cc66c3b | |||
| 83e7b65e25 | |||
| 58388b06b4 | |||
| 8201e2f5c2 | |||
| 0742d3bc45 | |||
| 625ed4d6f5 | |||
| 5512a9e454 | |||
| 29b17ee328 | |||
| c06d4af7b6 | |||
| 27397143db | |||
| 665c26cc5d | |||
| 922e0ceb9b | |||
| ae1536768a | |||
| 78ded9e242 | |||
| 4f10840f04 | |||
| 9ca343c34e | |||
| ce2f24d73f | |||
| 65bc28d1f2 | |||
| 45e2eccb11 | |||
| b38adf894d | |||
| dd6f84fdd2 | |||
| 99e758fcbb | |||
| b0720ccd8c | |||
| 459824dbec | |||
| b08cad0123 | |||
| c7a978e0aa | |||
| 76aa33612c | |||
| e8db041d58 | |||
| b3f26856c4 | |||
| 26ab74bd9a | |||
| 17afc91850 | |||
| cbc996fb30 | |||
| bf1430d5b6 | |||
| e9a6cd049a | |||
| 60ed8fe080 | |||
| 56faee0b44 | |||
| abb7488895 | |||
| e851c0c834 | |||
| 58d9c0c400 | |||
| be1259a9f0 | |||
| 3af6328400 | |||
| 39aaafcb6c | |||
| 9ac6f27ece | |||
| 9f6a0efec9 | |||
| cdc10bc3ab | |||
| 9ab2bdd64f | |||
| 079a3f7f42 | |||
| cf7806b604 | |||
| dd92b911d7 | |||
| bac1b43f10 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": true,
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
Add support E2B code interpreter tool for FastAPI
|
||||
@@ -24,39 +24,46 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
- uses: pnpm/action-setup@v2
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
working-directory: .
|
||||
|
||||
- name: Build create-llama
|
||||
run: pnpm run build
|
||||
working-directory: .
|
||||
- name: Pack
|
||||
run: pnpm pack --pack-destination ./output
|
||||
working-directory: .
|
||||
- name: Extract Pack
|
||||
run: tar -xvzf ./output/*.tgz -C ./output
|
||||
|
||||
- name: Install
|
||||
run: pnpm run pack-install
|
||||
working-directory: .
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: pnpm exec playwright test
|
||||
run: pnpm run e2e
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
working-directory: .
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
|
||||
@@ -13,17 +13,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Run lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Run Prettier
|
||||
run: pnpm run format
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Publish to GitHub Releases
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build tarball
|
||||
run: |
|
||||
pnpm pack
|
||||
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "create-llama-*.tgz"
|
||||
name: Release ${{ github.ref }}
|
||||
bodyFile: "CHANGELOG.md"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Add auth token to .npmrc file
|
||||
run: |
|
||||
cat << EOF >> ".npmrc"
|
||||
//registry.npmjs.org/:_authToken=$NPM_TOKEN
|
||||
EOF
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Get changeset status
|
||||
id: get-changeset-status
|
||||
run: |
|
||||
pnpm changeset status --output .changeset/status.json
|
||||
new_version=$(jq -r '.releases[0].newVersion' < .changeset/status.json)
|
||||
rm -v .changeset/status.json
|
||||
echo "new-version=${new_version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
commit: Release ${{ steps.get-changeset-status.outputs.new-version }}
|
||||
title: Release ${{ steps.get-changeset-status.outputs.new-version }}
|
||||
# build package and call changeset publish
|
||||
publish: pnpm release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -45,3 +45,6 @@ e2e/cache
|
||||
|
||||
# intellij
|
||||
**/.idea
|
||||
|
||||
# build artifacts
|
||||
create-llama-*.tgz
|
||||
|
||||
+103
@@ -1,5 +1,108 @@
|
||||
# create-llama
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cd50a33: Add interpreter tool for TS using e2b.dev
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 260d37a: Add system prompt env variable for TS
|
||||
- bbd5b8d: Fix postgres connection leaking issue
|
||||
- bb53425: Support HTTP proxies by setting the GLOBAL_AGENT_HTTP_PROXY env variable
|
||||
- 69c2e16: Fix streaming for Express
|
||||
- 7873bfb: Update Ollama provider to run with the base URL from the environment variable
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 56537a1: Display PDF files in source nodes
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 84db798: feat: support display latex in chat markdown
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0bc8e75: Use ingestion pipeline for dedicated vector stores (Python only)
|
||||
- cb1001d: Add ChromaDB vector store
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 416073d: Directly import vector stores to work with NextJS
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 056e376: Add support for displaying tool outputs (including weather widget as example)
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7bd3ed5: Support Anthropic and Gemini as model providers
|
||||
- 7bd3ed5: Support new agents from LITS 0.3
|
||||
- cfb5257: Display events (e.g. retrieving nodes) per chat message
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- f1c3e8d: Add Llama3 and Phi3 support using Ollama
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a0dec80: Use `gpt-4-turbo` model as default. Upgrade Python llama-index to 0.10.28
|
||||
- 753229d: Remove asking for AI models and use defaults instead (OpenAIs GPT-4 Vision Preview and Embeddings v3). Use `--ask-models` CLI parameter to select models.
|
||||
- 1d78202: Add observability for Python
|
||||
- 6acccd2: Use poetry run generate to generate embeddings for FastAPI
|
||||
- 9efcffe: Use Settings object for LlamaIndex configuration
|
||||
- 418bf9b: refactor: use tsx instead of ts-node
|
||||
- 1be69a5: Add Qdrant support
|
||||
|
||||
## 0.0.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 625ed4d: Support Astra VectorDB
|
||||
- 922e0ce: Remove UI question (use shadcn as default). Use `html` UI by calling create-llama with --ui html parameter
|
||||
- ce2f24d: Update loaders and tools config to yaml format (for Python)
|
||||
- e8db041: Let user select multiple datasources (URLs, files and folders)
|
||||
- c06d4af: Add nodes to the response (Python)
|
||||
- 29b17ee: Allow using agents without any data source
|
||||
- 665c26c: Add redirect to documentation page when accessing the base URL (FastAPI)
|
||||
- 78ded9e: Add Dockerfile templates for Typescript and Python
|
||||
- 99e758f: Merge non-streaming and streaming template to one
|
||||
- b3f2685: Add support for agent generation for Typescript
|
||||
- 2739714: Use a database (MySQL or PostgreSQL) as a data source
|
||||
|
||||
## 0.0.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 56faee0: Added windows e2e tests
|
||||
- 60ed8fe: Added missing environment variable config for URL data source
|
||||
- 60ed8fe: Fixed tool usage by freezing llama-index package versions
|
||||
|
||||
## 0.0.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3af6328: Add support for llamaparse using Typescript
|
||||
- dd92b91: Add fetching llm and embedding models from server
|
||||
- bac1b43: Add Milvus vector database
|
||||
|
||||
## 0.0.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Contributing
|
||||
|
||||
## Getting Started
|
||||
|
||||
Install NodeJS. Preferably v18 using nvm or n.
|
||||
|
||||
Inside the `create-llama` directory:
|
||||
|
||||
```
|
||||
npm i -g pnpm
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Note: we use pnpm in this repo, which has a lot of the same functionality and CLI options as npm but it does do some things better, like caching.
|
||||
|
||||
### Building
|
||||
|
||||
When we publish to NPM we will have a [ncc](https://github.com/vercel/ncc) compiled version of the tool. To run the build command, run
|
||||
|
||||
```
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
### Test cases
|
||||
|
||||
We are using a set of e2e tests to ensure that the tool works as expected.
|
||||
|
||||
We're using [playwright](https://playwright.dev/) to run the tests.
|
||||
To install it, call:
|
||||
|
||||
```
|
||||
pnpm exec playwright install --with-deps
|
||||
```
|
||||
|
||||
Then you can create a global `create-llama` command (used by the e2e tests) that is linked to your local dev environment (if you update the build, you don't need to re-link):
|
||||
|
||||
```
|
||||
pnpm link --global
|
||||
```
|
||||
|
||||
And then finally run the tests:
|
||||
|
||||
```
|
||||
pnpm run e2e
|
||||
```
|
||||
|
||||
To write new test cases write them in [e2e](/e2e)
|
||||
|
||||
## Changeset
|
||||
|
||||
We use [changesets](https://github.com/changesets/changesets) for managing versions and changelogs. To create a new changeset, run:
|
||||
|
||||
```
|
||||
pnpm changeset
|
||||
```
|
||||
|
||||
Please send a descriptive changeset for each PR.
|
||||
|
||||
## Publishing (maintainers only)
|
||||
|
||||
To publish a new version of the library, first create a new version:
|
||||
|
||||
```shell
|
||||
pnpm new-version
|
||||
```
|
||||
|
||||
If everything looks good, commit the generated files and release the new version:
|
||||
|
||||
```shell
|
||||
pnpm release
|
||||
git push # push to the main branch
|
||||
git push --tags
|
||||
```
|
||||
@@ -18,25 +18,19 @@ to start the development server. You can then visit [http://localhost:3000](http
|
||||
|
||||
## What you'll get
|
||||
|
||||
- A Next.js-powered front-end. The app is set up as a chat interface that can answer questions about your data (see below)
|
||||
- You can style it with HTML and CSS, or you can optionally use components from [shadcn/ui](https://ui.shadcn.com/)
|
||||
- A Next.js-powered front-end using components from [shadcn/ui](https://ui.shadcn.com/). The app is set up as a chat interface that can answer questions about your data (see below)
|
||||
- Your choice of 3 back-ends:
|
||||
- **Next.js**: if you select this option, you’ll have a full stack Next.js application that you can deploy to a host like [Vercel](https://vercel.com/) in just a few clicks. This uses [LlamaIndex.TS](https://www.npmjs.com/package/llamaindex), our TypeScript library.
|
||||
- **Next.js**: if you select this option, you’ll have a full-stack Next.js application that you can deploy to a host like [Vercel](https://vercel.com/) in just a few clicks. This uses [LlamaIndex.TS](https://www.npmjs.com/package/llamaindex), our TypeScript library.
|
||||
- **Express**: if you want a more traditional Node.js application you can generate an Express backend. This also uses LlamaIndex.TS.
|
||||
- **Python FastAPI**: if you select this option you’ll get a backend powered by the [llama-index python package](https://pypi.org/project/llama-index/), which you can deploy to a service like Render or fly.io.
|
||||
- The back-end has a single endpoint that allows you to send the state of your chat and receive additional responses
|
||||
- You can choose whether you want a streaming or non-streaming back-end (if you're not sure, we recommend streaming)
|
||||
- You can choose whether you want to use `ContextChatEngine` or `SimpleChatEngine`
|
||||
- `SimpleChatEngine` will just talk to the LLM directly without using your data
|
||||
- `ContextChatEngine` will use your data to answer questions (see below).
|
||||
- **Python FastAPI**: if you select this option, you’ll get a backend powered by the [llama-index python package](https://pypi.org/project/llama-index/), which you can deploy to a service like Render or fly.io.
|
||||
- The back-end has two endpoints (one streaming, the other one non-streaming) that allow you to send the state of your chat and receive additional responses
|
||||
- You add arbitrary data sources to your chat, like local files, websites, or data retrieved from a database.
|
||||
- Turn your chat into an AI agent by adding tools (functions called by the LLM).
|
||||
- The app uses OpenAI by default, so you'll need an OpenAI API key, or you can customize it to use any of the dozens of LLMs we support.
|
||||
|
||||
## Using your data
|
||||
|
||||
If you've enabled `ContextChatEngine`, you can supply your own data and the app will index it and answer questions. Your generated app will have a folder called `data`:
|
||||
|
||||
- With the Next.js backend this is `./data`
|
||||
- With the Express or Python backend this is in `./backend/data`
|
||||
You can supply your own data; the app will index it and answer questions. Your generated app will have a folder called `data` (If you're using Express or Python and generate a frontend, it will be `./backend/data`).
|
||||
|
||||
The app will ingest any supported files you put in this directory. Your Next.js and Express apps use LlamaIndex.TS so they will be able to ingest any PDF, text, CSV, Markdown, Word and HTML files. The Python backend can read even more types, including video and audio files.
|
||||
|
||||
@@ -46,22 +40,28 @@ Before you can use your data, you need to index it. If you're using the Next.js
|
||||
npm run generate
|
||||
```
|
||||
|
||||
Then re-start your app. Remember you'll need to re-run `generate` if you add new files to your `data` folder. If you're using the Python backend, you can trigger indexing of your data by deleting the `./storage` folder and re-starting the app.
|
||||
Then re-start your app. Remember you'll need to re-run `generate` if you add new files to your `data` folder.
|
||||
|
||||
## Don't want a front-end?
|
||||
If you're using the Python backend, you can trigger indexing of your data by calling:
|
||||
|
||||
It's optional! If you've selected the Python or Express back-ends, just delete the `frontend` folder and you'll get an API without any front-end code.
|
||||
```bash
|
||||
poetry run generate
|
||||
```
|
||||
|
||||
## Customizing the LLM
|
||||
## Want a front-end?
|
||||
|
||||
By default the app will use OpenAI's gpt-3.5-turbo model. If you want to use GPT-4, you can modify this by editing a file:
|
||||
Optionally generate a frontend if you've selected the Python or Express back-ends. If you do so, `create-llama` will generate two folders: `frontend`, for your Next.js-based frontend code, and `backend` containing your API.
|
||||
|
||||
- In the Next.js backend, edit `./app/api/chat/route.ts` and replace `gpt-3.5-turbo` with `gpt-4`
|
||||
- In the Express backend, edit `./backend/src/controllers/chat.controller.ts` and likewise replace `gpt-3.5-turbo` with `gpt-4`
|
||||
- In the Python backend, edit `./backend/app/utils/index.py` and once again replace `gpt-3.5-turbo` with `gpt-4`
|
||||
## Customizing the AI models
|
||||
|
||||
The app will default to OpenAI's `gpt-4-turbo` LLM and `text-embedding-3-large` embedding model.
|
||||
|
||||
If you want to use different OpenAI models, add the `--ask-models` CLI parameter.
|
||||
|
||||
You can also replace OpenAI with one of our [dozens of other supported LLMs](https://docs.llamaindex.ai/en/stable/module_guides/models/llms/modules.html).
|
||||
|
||||
To do so, you have to manually change the generated code (edit the `settings.ts` file for Typescript projects or the `settings.py` file for Python projects)
|
||||
|
||||
## Example
|
||||
|
||||
The simplest thing to do is run `create-llama` in interactive mode:
|
||||
@@ -84,13 +84,19 @@ Need to install the following packages:
|
||||
create-llama@latest
|
||||
Ok to proceed? (y) y
|
||||
✔ What is your project named? … my-app
|
||||
✔ Which template would you like to use? › Chat with streaming
|
||||
✔ Which template would you like to use? › Chat
|
||||
✔ Which framework would you like to use? › NextJS
|
||||
✔ Which UI would you like to use? › Just HTML
|
||||
✔ Which chat engine would you like to use? › ContextChatEngine
|
||||
✔ Would you like to set up observability? › No
|
||||
✔ Please provide your OpenAI API key (leave blank to skip): …
|
||||
✔ Would you like to use ESLint? … No / Yes
|
||||
Creating a new LlamaIndex app in /home/my-app.
|
||||
✔ Which data source would you like to use? › Use an example PDF
|
||||
✔ Would you like to add another data source? › No
|
||||
✔ Would you like to use LlamaParse (improved parser for RAG - requires API key)? … no / yes
|
||||
✔ Would you like to use a vector database? › No, just store the data in the file system
|
||||
? How would you like to proceed? › - Use arrow-keys. Return to submit.
|
||||
Just generate code (~1 sec)
|
||||
❯ Start in VSCode (~1 sec)
|
||||
Generate code and install dependencies (~2 min)
|
||||
Generate code, install dependencies, and run the app (~2 min)
|
||||
```
|
||||
|
||||
### Running non-interactively
|
||||
|
||||
+10
-14
@@ -26,23 +26,20 @@ export type InstallAppArgs = Omit<
|
||||
export async function createApp({
|
||||
template,
|
||||
framework,
|
||||
engine,
|
||||
ui,
|
||||
appPath,
|
||||
packageManager,
|
||||
eslint,
|
||||
frontend,
|
||||
openAiKey,
|
||||
modelConfig,
|
||||
llamaCloudKey,
|
||||
model,
|
||||
embeddingModel,
|
||||
communityProjectConfig,
|
||||
llamapack,
|
||||
vectorDb,
|
||||
externalPort,
|
||||
postInstallAction,
|
||||
dataSource,
|
||||
dataSources,
|
||||
tools,
|
||||
useLlamaParse,
|
||||
observability,
|
||||
}: InstallAppArgs): Promise<void> {
|
||||
const root = path.resolve(appPath);
|
||||
@@ -75,22 +72,19 @@ export async function createApp({
|
||||
root,
|
||||
template,
|
||||
framework,
|
||||
engine,
|
||||
ui,
|
||||
packageManager,
|
||||
isOnline,
|
||||
eslint,
|
||||
openAiKey,
|
||||
modelConfig,
|
||||
llamaCloudKey,
|
||||
model,
|
||||
embeddingModel,
|
||||
communityProjectConfig,
|
||||
llamapack,
|
||||
vectorDb,
|
||||
externalPort,
|
||||
postInstallAction,
|
||||
dataSource,
|
||||
dataSources,
|
||||
tools,
|
||||
useLlamaParse,
|
||||
observability,
|
||||
};
|
||||
|
||||
@@ -127,11 +121,13 @@ export async function createApp({
|
||||
}
|
||||
|
||||
if (toolsRequireConfig(tools)) {
|
||||
const configFile =
|
||||
framework === "fastapi" ? "config/tools.yaml" : "config/tools.json";
|
||||
console.log(
|
||||
yellow(
|
||||
`You have selected tools that require configuration. Please configure them in the ${terminalLink(
|
||||
"tools_config.json",
|
||||
`file://${root}/tools_config.json`,
|
||||
configFile,
|
||||
`file://${root}/${configFile}`,
|
||||
)} file.`,
|
||||
),
|
||||
);
|
||||
|
||||
+9
-25
@@ -4,7 +4,6 @@ import { ChildProcess } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import type {
|
||||
TemplateEngine,
|
||||
TemplateFramework,
|
||||
TemplatePostInstallAction,
|
||||
TemplateType,
|
||||
@@ -12,13 +11,13 @@ import type {
|
||||
} from "../helpers";
|
||||
import { createTestDir, runCreateLlama, type AppType } from "./utils";
|
||||
|
||||
const templateTypes: TemplateType[] = ["streaming", "simple"];
|
||||
const templateTypes: TemplateType[] = ["streaming"];
|
||||
const templateFrameworks: TemplateFramework[] = [
|
||||
"nextjs",
|
||||
"express",
|
||||
"fastapi",
|
||||
];
|
||||
const templateEngines: TemplateEngine[] = ["simple", "context"];
|
||||
const dataSources: string[] = ["--no-files", "--example-file"];
|
||||
const templateUIs: TemplateUI[] = ["shadcn", "html"];
|
||||
const templatePostInstallActions: TemplatePostInstallAction[] = [
|
||||
"none",
|
||||
@@ -27,24 +26,12 @@ const templatePostInstallActions: TemplatePostInstallAction[] = [
|
||||
|
||||
for (const templateType of templateTypes) {
|
||||
for (const templateFramework of templateFrameworks) {
|
||||
for (const templateEngine of templateEngines) {
|
||||
for (const dataSource of dataSources) {
|
||||
for (const templateUI of templateUIs) {
|
||||
for (const templatePostInstallAction of templatePostInstallActions) {
|
||||
if (templateFramework === "nextjs" && templateType === "simple") {
|
||||
// nextjs doesn't support simple templates - skip tests
|
||||
continue;
|
||||
}
|
||||
const appType: AppType =
|
||||
templateFramework === "express" || templateFramework === "fastapi"
|
||||
? templateType === "simple"
|
||||
? "--no-frontend" // simple templates don't have frontends
|
||||
: "--frontend"
|
||||
: "";
|
||||
if (appType === "--no-frontend" && templateUI !== "html") {
|
||||
// if there's no frontend, don't iterate over UIs
|
||||
continue;
|
||||
}
|
||||
test.describe(`try create-llama ${templateType} ${templateFramework} ${templateEngine} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
|
||||
templateFramework === "nextjs" ? "" : "--frontend";
|
||||
test.describe(`try create-llama ${templateType} ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
|
||||
let port: number;
|
||||
let externalPort: number;
|
||||
let cwd: string;
|
||||
@@ -61,7 +48,7 @@ for (const templateType of templateTypes) {
|
||||
cwd,
|
||||
templateType,
|
||||
templateFramework,
|
||||
templateEngine,
|
||||
dataSource,
|
||||
templateUI,
|
||||
vectorDb,
|
||||
appType,
|
||||
@@ -79,7 +66,6 @@ for (const templateType of templateTypes) {
|
||||
});
|
||||
test("Frontend should have a title", async ({ page }) => {
|
||||
test.skip(templatePostInstallAction !== "runApp");
|
||||
test.skip(appType === "--no-frontend");
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
|
||||
});
|
||||
@@ -88,7 +74,6 @@ for (const templateType of templateTypes) {
|
||||
page,
|
||||
}) => {
|
||||
test.skip(templatePostInstallAction !== "runApp");
|
||||
test.skip(appType === "--no-frontend");
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await page.fill("form input", "hello");
|
||||
const [response] = await Promise.all([
|
||||
@@ -109,14 +94,13 @@ for (const templateType of templateTypes) {
|
||||
expect(response.ok()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Backend should response when calling API", async ({
|
||||
test("Backend frameworks should response when calling non-streaming chat API", async ({
|
||||
request,
|
||||
}) => {
|
||||
test.skip(templatePostInstallAction !== "runApp");
|
||||
test.skip(appType !== "--no-frontend");
|
||||
const backendPort = appType === "" ? port : externalPort;
|
||||
test.skip(templateFramework === "nextjs");
|
||||
const response = await request.post(
|
||||
`http://localhost:${backendPort}/api/chat`,
|
||||
`http://localhost:${externalPort}/api/chat/request`,
|
||||
{
|
||||
data: {
|
||||
messages: [
|
||||
|
||||
+12
-25
@@ -4,7 +4,6 @@ import { mkdir } from "node:fs/promises";
|
||||
import * as path from "path";
|
||||
import waitPort from "wait-port";
|
||||
import {
|
||||
TemplateEngine,
|
||||
TemplateFramework,
|
||||
TemplatePostInstallAction,
|
||||
TemplateType,
|
||||
@@ -13,8 +12,7 @@ import {
|
||||
} from "../helpers";
|
||||
|
||||
export type AppType = "--frontend" | "--no-frontend" | "";
|
||||
const MODEL = "gpt-3.5-turbo";
|
||||
const EMBEDDING_MODEL = "text-embedding-ada-002";
|
||||
|
||||
export type CreateLlamaResult = {
|
||||
projectName: string;
|
||||
appProcess: ChildProcess;
|
||||
@@ -67,7 +65,7 @@ export async function runCreateLlama(
|
||||
cwd: string,
|
||||
templateType: TemplateType,
|
||||
templateFramework: TemplateFramework,
|
||||
templateEngine: TemplateEngine,
|
||||
dataSource: string,
|
||||
templateUI: TemplateUI,
|
||||
vectorDb: TemplateVectorDB,
|
||||
appType: AppType,
|
||||
@@ -75,45 +73,32 @@ export async function runCreateLlama(
|
||||
externalPort: number,
|
||||
postInstallAction: TemplatePostInstallAction,
|
||||
): Promise<CreateLlamaResult> {
|
||||
const createLlama = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"output",
|
||||
"package",
|
||||
"dist",
|
||||
"index.js",
|
||||
);
|
||||
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
throw new Error("Setting OPENAI_API_KEY is mandatory to run tests");
|
||||
}
|
||||
const name = [
|
||||
templateType,
|
||||
templateFramework,
|
||||
templateEngine,
|
||||
dataSource,
|
||||
templateUI,
|
||||
appType,
|
||||
].join("-");
|
||||
const command = [
|
||||
"node",
|
||||
createLlama,
|
||||
"create-llama",
|
||||
name,
|
||||
"--template",
|
||||
templateType,
|
||||
"--framework",
|
||||
templateFramework,
|
||||
"--engine",
|
||||
templateEngine,
|
||||
dataSource,
|
||||
"--ui",
|
||||
templateUI,
|
||||
"--vector-db",
|
||||
vectorDb,
|
||||
"--model",
|
||||
MODEL,
|
||||
"--embedding-model",
|
||||
EMBEDDING_MODEL,
|
||||
"--open-ai-key",
|
||||
process.env.OPENAI_API_KEY || "testKey",
|
||||
process.env.OPENAI_API_KEY,
|
||||
appType,
|
||||
"--eslint",
|
||||
"--use-npm",
|
||||
"--use-pnpm",
|
||||
"--port",
|
||||
port,
|
||||
"--external-port",
|
||||
@@ -123,6 +108,8 @@ export async function runCreateLlama(
|
||||
"--tools",
|
||||
"none",
|
||||
"--no-llama-parse",
|
||||
"--observability",
|
||||
"none",
|
||||
].join(" ");
|
||||
console.log(`running command '${command}' in ${cwd}`);
|
||||
const appProcess = exec(command, {
|
||||
|
||||
@@ -48,3 +48,21 @@ export const copy = async (
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const assetRelocator = (name: string) => {
|
||||
switch (name) {
|
||||
case "gitignore":
|
||||
case "npmrc":
|
||||
case "eslintrc.json": {
|
||||
return `.${name}`;
|
||||
}
|
||||
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
|
||||
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
|
||||
case "README-template.md": {
|
||||
return "README.md";
|
||||
}
|
||||
default: {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import yaml, { Document } from "yaml";
|
||||
import { templatesDir } from "./dir";
|
||||
import { DbSourceConfig, TemplateDataSource, WebSourceConfig } from "./types";
|
||||
|
||||
export const EXAMPLE_FILE: TemplateDataSource = {
|
||||
type: "file",
|
||||
config: {
|
||||
path: path.join(templatesDir, "components", "data", "101.pdf"),
|
||||
},
|
||||
};
|
||||
|
||||
export function getDataSources(
|
||||
files?: string,
|
||||
exampleFile?: boolean,
|
||||
): TemplateDataSource[] | undefined {
|
||||
let dataSources: TemplateDataSource[] | undefined = undefined;
|
||||
if (files) {
|
||||
// If user specified files option, then the program should use context engine
|
||||
dataSources = files.split(",").map((filePath) => ({
|
||||
type: "file",
|
||||
config: {
|
||||
path: filePath,
|
||||
},
|
||||
}));
|
||||
}
|
||||
if (exampleFile) {
|
||||
dataSources = [...(dataSources ? dataSources : []), EXAMPLE_FILE];
|
||||
}
|
||||
return dataSources;
|
||||
}
|
||||
|
||||
export async function writeLoadersConfig(
|
||||
root: string,
|
||||
dataSources: TemplateDataSource[],
|
||||
useLlamaParse?: boolean,
|
||||
) {
|
||||
if (dataSources.length === 0) return; // no datasources, no config needed
|
||||
const loaderConfig = new Document({});
|
||||
// Web loader config
|
||||
if (dataSources.some((ds) => ds.type === "web")) {
|
||||
const webLoaderConfig = new Document({});
|
||||
|
||||
// Create config for browser driver arguments
|
||||
const driverArgNodeValue = webLoaderConfig.createNode([
|
||||
"--no-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
]);
|
||||
driverArgNodeValue.commentBefore =
|
||||
" The arguments to pass to the webdriver. E.g.: add --headless to run in headless mode";
|
||||
webLoaderConfig.set("driver_arguments", driverArgNodeValue);
|
||||
|
||||
// Create config for urls
|
||||
const urlConfigs = dataSources
|
||||
.filter((ds) => ds.type === "web")
|
||||
.map((ds) => {
|
||||
const dsConfig = ds.config as WebSourceConfig;
|
||||
return {
|
||||
base_url: dsConfig.baseUrl,
|
||||
prefix: dsConfig.prefix,
|
||||
depth: dsConfig.depth,
|
||||
};
|
||||
});
|
||||
const urlConfigNode = webLoaderConfig.createNode(urlConfigs);
|
||||
urlConfigNode.commentBefore = ` base_url: The URL to start crawling with
|
||||
prefix: Only crawl URLs matching the specified prefix
|
||||
depth: The maximum depth for BFS traversal
|
||||
You can add more websites by adding more entries (don't forget the - prefix from YAML)`;
|
||||
webLoaderConfig.set("urls", urlConfigNode);
|
||||
|
||||
// Add web config to the loaders config
|
||||
loaderConfig.set("web", webLoaderConfig);
|
||||
}
|
||||
|
||||
// File loader config
|
||||
if (dataSources.some((ds) => ds.type === "file")) {
|
||||
// Add documentation to web loader config
|
||||
const node = loaderConfig.createNode({
|
||||
use_llama_parse: useLlamaParse,
|
||||
});
|
||||
node.commentBefore = ` use_llama_parse: Use LlamaParse if \`true\`. Needs a \`LLAMA_CLOUD_API_KEY\` from https://cloud.llamaindex.ai set as environment variable`;
|
||||
loaderConfig.set("file", node);
|
||||
}
|
||||
|
||||
// DB loader config
|
||||
const dbLoaders = dataSources.filter((ds) => ds.type === "db");
|
||||
if (dbLoaders.length > 0) {
|
||||
const dbLoaderConfig = new Document({});
|
||||
const configEntries = dbLoaders.map((ds) => {
|
||||
const dsConfig = ds.config as DbSourceConfig;
|
||||
return {
|
||||
uri: dsConfig.uri,
|
||||
queries: [dsConfig.queries],
|
||||
};
|
||||
});
|
||||
|
||||
const node = dbLoaderConfig.createNode(configEntries);
|
||||
node.commentBefore = ` The configuration for the database loader, only supports MySQL and PostgreSQL databases for now.
|
||||
uri: The URI for the database. E.g.: mysql+pymysql://user:password@localhost:3306/db or postgresql+psycopg2://user:password@localhost:5432/db
|
||||
query: The query to fetch data from the database. E.g.: SELECT * FROM table`;
|
||||
loaderConfig.set("db", node);
|
||||
}
|
||||
|
||||
// Write loaders config
|
||||
const loaderConfigPath = path.join(root, "config", "loaders.yaml");
|
||||
await fs.mkdir(path.join(root, "config"), { recursive: true });
|
||||
await fs.writeFile(loaderConfigPath, yaml.stringify(loaderConfig));
|
||||
}
|
||||
@@ -46,13 +46,16 @@ export const writeDevcontainer = async (
|
||||
framework: TemplateFramework,
|
||||
frontend: boolean,
|
||||
) => {
|
||||
console.log("Adding .devcontainer");
|
||||
const devcontainerDir = path.join(root, ".devcontainer");
|
||||
if (fs.existsSync(devcontainerDir)) {
|
||||
console.log("Template already has a .devcontainer. Using it.");
|
||||
return;
|
||||
}
|
||||
const devcontainerContent = renderDevcontainerContent(
|
||||
templatesDir,
|
||||
framework,
|
||||
frontend,
|
||||
);
|
||||
const devcontainerDir = path.join(root, ".devcontainer");
|
||||
fs.mkdirSync(devcontainerDir);
|
||||
await fs.promises.writeFile(
|
||||
path.join(devcontainerDir, "devcontainer.json"),
|
||||
|
||||
+254
-118
@@ -1,13 +1,14 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { TOOL_SYSTEM_PROMPT_ENV_VAR, Tool } from "./tools";
|
||||
import {
|
||||
FileSourceConfig,
|
||||
ModelConfig,
|
||||
TemplateDataSource,
|
||||
TemplateFramework,
|
||||
TemplateVectorDB,
|
||||
} from "./types";
|
||||
|
||||
type EnvVar = {
|
||||
export type EnvVar = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
value?: string;
|
||||
@@ -29,14 +30,20 @@ const renderEnvVar = (envVars: EnvVar[]): string => {
|
||||
);
|
||||
};
|
||||
|
||||
const getVectorDBEnvs = (vectorDb: TemplateVectorDB) => {
|
||||
const getVectorDBEnvs = (
|
||||
vectorDb?: TemplateVectorDB,
|
||||
framework?: TemplateFramework,
|
||||
): EnvVar[] => {
|
||||
if (!vectorDb || !framework) {
|
||||
return [];
|
||||
}
|
||||
switch (vectorDb) {
|
||||
case "mongo":
|
||||
return [
|
||||
{
|
||||
name: "MONGO_URI",
|
||||
name: "MONGODB_URI",
|
||||
description:
|
||||
"For generating a connection URI, see https://docs.timescale.com/use-timescale/latest/services/create-a-service\nThe MongoDB connection URI.",
|
||||
"For generating a connection URI, see https://www.mongodb.com/docs/manual/reference/connection-string/ \nThe MongoDB connection URI.",
|
||||
},
|
||||
{
|
||||
name: "MONGODB_DATABASE",
|
||||
@@ -71,70 +78,179 @@ const getVectorDBEnvs = (vectorDb: TemplateVectorDB) => {
|
||||
name: "PINECONE_INDEX_NAME",
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const getDataSourceEnvs = (dataSource: TemplateDataSource) => {
|
||||
switch (dataSource.type) {
|
||||
case "web":
|
||||
case "milvus":
|
||||
return [
|
||||
{
|
||||
name: "BASE_URL",
|
||||
description: "The base URL to start web scraping.",
|
||||
name: "MILVUS_ADDRESS",
|
||||
description:
|
||||
"The address of the Milvus server. Eg: http://localhost:19530",
|
||||
value: "http://localhost:19530",
|
||||
},
|
||||
{
|
||||
name: "URL_PREFIX",
|
||||
description: "The prefix of the URL to start web scraping.",
|
||||
name: "MILVUS_COLLECTION",
|
||||
description:
|
||||
"The name of the Milvus collection to store the vectors.",
|
||||
value: "llamacollection",
|
||||
},
|
||||
{
|
||||
name: "MAX_DEPTH",
|
||||
description: "The maximum depth to scrape.",
|
||||
name: "MILVUS_USERNAME",
|
||||
description: "The username to access the Milvus server.",
|
||||
},
|
||||
{
|
||||
name: "MILVUS_PASSWORD",
|
||||
description: "The password to access the Milvus server.",
|
||||
},
|
||||
];
|
||||
case "astra":
|
||||
return [
|
||||
{
|
||||
name: "ASTRA_DB_APPLICATION_TOKEN",
|
||||
description: "The generated app token for your Astra database",
|
||||
},
|
||||
{
|
||||
name: "ASTRA_DB_ENDPOINT",
|
||||
description: "The API endpoint for your Astra database",
|
||||
},
|
||||
{
|
||||
name: "ASTRA_DB_COLLECTION",
|
||||
description: "The name of the collection in your Astra database",
|
||||
},
|
||||
];
|
||||
case "qdrant":
|
||||
return [
|
||||
{
|
||||
name: "QDRANT_URL",
|
||||
description:
|
||||
"The qualified REST URL of the Qdrant server. Eg: http://localhost:6333",
|
||||
},
|
||||
{
|
||||
name: "QDRANT_COLLECTION",
|
||||
description: "The name of Qdrant collection to use.",
|
||||
},
|
||||
{
|
||||
name: "QDRANT_API_KEY",
|
||||
description:
|
||||
"Optional API key for authenticating requests to Qdrant.",
|
||||
},
|
||||
];
|
||||
case "chroma":
|
||||
const envs = [
|
||||
{
|
||||
name: "CHROMA_COLLECTION",
|
||||
description: "The name of the collection in your Chroma database",
|
||||
},
|
||||
{
|
||||
name: "CHROMA_HOST",
|
||||
description: "The API endpoint for your Chroma database",
|
||||
},
|
||||
{
|
||||
name: "CHROMA_PORT",
|
||||
description: "The port for your Chroma database",
|
||||
},
|
||||
];
|
||||
// TS Version doesn't support config local storage path
|
||||
if (framework === "fastapi") {
|
||||
envs.push({
|
||||
name: "CHROMA_PATH",
|
||||
description: `The local path to the Chroma database.
|
||||
Specify this if you are using a local Chroma database.
|
||||
Otherwise, use CHROMA_HOST and CHROMA_PORT config above`,
|
||||
});
|
||||
}
|
||||
return envs;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const createBackendEnvFile = async (
|
||||
root: string,
|
||||
opts: {
|
||||
openAiKey?: string;
|
||||
llamaCloudKey?: string;
|
||||
vectorDb?: TemplateVectorDB;
|
||||
model?: string;
|
||||
embeddingModel?: string;
|
||||
framework?: TemplateFramework;
|
||||
dataSource?: TemplateDataSource;
|
||||
port?: number;
|
||||
},
|
||||
) => {
|
||||
// Init env values
|
||||
const envFileName = ".env";
|
||||
const defaultEnvs = [
|
||||
const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
|
||||
return [
|
||||
{
|
||||
name: "MODEL_PROVIDER",
|
||||
description: "The provider for the AI models to use.",
|
||||
value: modelConfig.provider,
|
||||
},
|
||||
{
|
||||
render: true,
|
||||
name: "MODEL",
|
||||
description: "The name of LLM model to use.",
|
||||
value: opts.model || "gpt-3.5-turbo",
|
||||
value: modelConfig.model,
|
||||
},
|
||||
{
|
||||
render: true,
|
||||
name: "OPENAI_API_KEY",
|
||||
description: "The OpenAI API key to use.",
|
||||
value: opts.openAiKey,
|
||||
name: "EMBEDDING_MODEL",
|
||||
description: "Name of the embedding model to use.",
|
||||
value: modelConfig.embeddingModel,
|
||||
},
|
||||
// Add vector database environment variables
|
||||
...(opts.vectorDb ? getVectorDBEnvs(opts.vectorDb) : []),
|
||||
// Add data source environment variables
|
||||
...(opts.dataSource ? getDataSourceEnvs(opts.dataSource) : []),
|
||||
{
|
||||
name: "EMBEDDING_DIM",
|
||||
description: "Dimension of the embedding model to use.",
|
||||
value: modelConfig.dimensions.toString(),
|
||||
},
|
||||
...(modelConfig.provider === "openai"
|
||||
? [
|
||||
{
|
||||
name: "OPENAI_API_KEY",
|
||||
description: "The OpenAI API key to use.",
|
||||
value: modelConfig.apiKey,
|
||||
},
|
||||
{
|
||||
name: "LLM_TEMPERATURE",
|
||||
description: "Temperature for sampling from the model.",
|
||||
},
|
||||
{
|
||||
name: "LLM_MAX_TOKENS",
|
||||
description: "Maximum number of tokens to generate.",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(modelConfig.provider === "anthropic"
|
||||
? [
|
||||
{
|
||||
name: "ANTHROPIC_API_KEY",
|
||||
description: "The Anthropic API key to use.",
|
||||
value: modelConfig.apiKey,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(modelConfig.provider === "gemini"
|
||||
? [
|
||||
{
|
||||
name: "GOOGLE_API_KEY",
|
||||
description: "The Google API key to use.",
|
||||
value: modelConfig.apiKey,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(modelConfig.provider === "ollama"
|
||||
? [
|
||||
{
|
||||
name: "OLLAMA_BASE_URL",
|
||||
description:
|
||||
"The base URL for the Ollama API. Eg: http://127.0.0.1:11434",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
let envVars: EnvVar[] = [];
|
||||
if (opts.framework === "fastapi") {
|
||||
envVars = [
|
||||
...defaultEnvs,
|
||||
};
|
||||
|
||||
const getFrameworkEnvs = (
|
||||
framework: TemplateFramework,
|
||||
port?: number,
|
||||
): EnvVar[] => {
|
||||
const sPort = port?.toString() || "8000";
|
||||
const result: EnvVar[] = [
|
||||
{
|
||||
name: "FILESERVER_URL_PREFIX",
|
||||
description:
|
||||
"FILESERVER_URL_PREFIX is the URL prefix of the server storing the images generated by the interpreter.",
|
||||
value:
|
||||
framework === "nextjs"
|
||||
? // FIXME: if we are using nextjs, port should be 3000
|
||||
"http://localhost:3000/api/files"
|
||||
: `http://localhost:${sPort}/api/files`,
|
||||
},
|
||||
];
|
||||
if (framework === "fastapi") {
|
||||
result.push(
|
||||
...[
|
||||
{
|
||||
name: "APP_HOST",
|
||||
@@ -144,67 +260,98 @@ export const createBackendEnvFile = async (
|
||||
{
|
||||
name: "APP_PORT",
|
||||
description: "The port to start the backend app.",
|
||||
value: opts.port?.toString() || "8000",
|
||||
value: sPort,
|
||||
},
|
||||
{
|
||||
name: "EMBEDDING_MODEL",
|
||||
description: "Name of the embedding model to use.",
|
||||
value: opts.embeddingModel,
|
||||
},
|
||||
{
|
||||
name: "EMBEDDING_DIM",
|
||||
description: "Dimension of the embedding model to use.",
|
||||
},
|
||||
{
|
||||
name: "LLM_TEMPERATURE",
|
||||
description: "Temperature for sampling from the model.",
|
||||
},
|
||||
{
|
||||
name: "LLM_MAX_TOKENS",
|
||||
description: "Maximum number of tokens to generate.",
|
||||
},
|
||||
{
|
||||
name: "TOP_K",
|
||||
description:
|
||||
"The number of similar embeddings to return when retrieving documents.",
|
||||
value: "3",
|
||||
},
|
||||
{
|
||||
name: "SYSTEM_PROMPT",
|
||||
description: `Custom system prompt.
|
||||
Example:
|
||||
SYSTEM_PROMPT="
|
||||
We have provided context information below.
|
||||
---------------------
|
||||
{context_str}
|
||||
---------------------
|
||||
Given this information, please answer the question: {query_str}
|
||||
"`,
|
||||
},
|
||||
(opts?.dataSource?.config as FileSourceConfig).useLlamaParse
|
||||
? {
|
||||
name: "LLAMA_CLOUD_API_KEY",
|
||||
description: `The Llama Cloud API key.`,
|
||||
value: opts.llamaCloudKey,
|
||||
}
|
||||
: {},
|
||||
],
|
||||
];
|
||||
} else {
|
||||
envVars = [
|
||||
...defaultEnvs,
|
||||
...[
|
||||
opts.framework === "nextjs"
|
||||
? {
|
||||
name: "NEXT_PUBLIC_MODEL",
|
||||
description:
|
||||
"The LLM model to use (hardcode to front-end artifact).",
|
||||
value: opts.model || "gpt-3.5-turbo",
|
||||
}
|
||||
: {},
|
||||
],
|
||||
];
|
||||
);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const getEngineEnvs = (): EnvVar[] => {
|
||||
return [
|
||||
{
|
||||
name: "TOP_K",
|
||||
description:
|
||||
"The number of similar embeddings to return when retrieving documents.",
|
||||
value: "3",
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const getToolEnvs = (tools?: Tool[]): EnvVar[] => {
|
||||
if (!tools?.length) return [];
|
||||
const toolEnvs: EnvVar[] = [];
|
||||
tools.forEach((tool) => {
|
||||
if (tool.envVars?.length) {
|
||||
toolEnvs.push(
|
||||
// Don't include the system prompt env var here
|
||||
// It should be handled separately by merging with the default system prompt
|
||||
...tool.envVars.filter(
|
||||
(env) => env.name !== TOOL_SYSTEM_PROMPT_ENV_VAR,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
return toolEnvs;
|
||||
};
|
||||
|
||||
const getSystemPromptEnv = (tools?: Tool[]): EnvVar => {
|
||||
const defaultSystemPrompt =
|
||||
"You are a helpful assistant who helps users with their questions.";
|
||||
|
||||
// build tool system prompt by merging all tool system prompts
|
||||
let toolSystemPrompt = "";
|
||||
tools?.forEach((tool) => {
|
||||
const toolSystemPromptEnv = tool.envVars?.find(
|
||||
(env) => env.name === TOOL_SYSTEM_PROMPT_ENV_VAR,
|
||||
);
|
||||
if (toolSystemPromptEnv) {
|
||||
toolSystemPrompt += toolSystemPromptEnv.value + "\n";
|
||||
}
|
||||
});
|
||||
|
||||
const systemPrompt = toolSystemPrompt
|
||||
? `\"${toolSystemPrompt}\"`
|
||||
: defaultSystemPrompt;
|
||||
|
||||
return {
|
||||
name: "SYSTEM_PROMPT",
|
||||
description: "The system prompt for the AI model.",
|
||||
value: systemPrompt,
|
||||
};
|
||||
};
|
||||
|
||||
export const createBackendEnvFile = async (
|
||||
root: string,
|
||||
opts: {
|
||||
llamaCloudKey?: string;
|
||||
vectorDb?: TemplateVectorDB;
|
||||
modelConfig: ModelConfig;
|
||||
framework: TemplateFramework;
|
||||
dataSources?: TemplateDataSource[];
|
||||
port?: number;
|
||||
tools?: Tool[];
|
||||
},
|
||||
) => {
|
||||
// Init env values
|
||||
const envFileName = ".env";
|
||||
const envVars: EnvVar[] = [
|
||||
{
|
||||
name: "LLAMA_CLOUD_API_KEY",
|
||||
description: `The Llama Cloud API key.`,
|
||||
value: opts.llamaCloudKey,
|
||||
},
|
||||
// Add model environment variables
|
||||
...getModelEnvs(opts.modelConfig),
|
||||
// Add engine environment variables
|
||||
...getEngineEnvs(),
|
||||
// Add vector database environment variables
|
||||
...getVectorDBEnvs(opts.vectorDb, opts.framework),
|
||||
...getFrameworkEnvs(opts.framework, opts.port),
|
||||
...getToolEnvs(opts.tools),
|
||||
getSystemPromptEnv(opts.tools),
|
||||
];
|
||||
// Render and write env file
|
||||
const content = renderEnvVar(envVars);
|
||||
await fs.writeFile(path.join(root, envFileName), content);
|
||||
@@ -215,20 +362,9 @@ export const createFrontendEnvFile = async (
|
||||
root: string,
|
||||
opts: {
|
||||
customApiPath?: string;
|
||||
model?: string;
|
||||
},
|
||||
) => {
|
||||
const defaultFrontendEnvs = [
|
||||
{
|
||||
name: "MODEL",
|
||||
description: "The OpenAI model to use.",
|
||||
value: opts.model,
|
||||
},
|
||||
{
|
||||
name: "NEXT_PUBLIC_MODEL",
|
||||
description: "The OpenAI model to use (hardcode to front-end artifact).",
|
||||
value: opts.model,
|
||||
},
|
||||
{
|
||||
name: "NEXT_PUBLIC_CHAT_API",
|
||||
description: "The backend API for chat endpoint.",
|
||||
|
||||
+62
-67
@@ -1,20 +1,21 @@
|
||||
import { copy } from "./copy";
|
||||
import { callPackageManager } from "./install";
|
||||
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { cyan } from "picocolors";
|
||||
|
||||
import { templatesDir } from "./dir";
|
||||
import fsExtra from "fs-extra";
|
||||
import { writeLoadersConfig } from "./datasources";
|
||||
import { createBackendEnvFile, createFrontendEnvFile } from "./env-variables";
|
||||
import { PackageManager } from "./get-pkg-manager";
|
||||
import { installLlamapackProject } from "./llama-pack";
|
||||
import { isHavingPoetryLockFile, tryPoetryRun } from "./poetry";
|
||||
import { installPythonTemplate } from "./python";
|
||||
import { downloadAndExtractRepo } from "./repo";
|
||||
import { ConfigFileType, writeToolsConfig } from "./tools";
|
||||
import {
|
||||
FileSourceConfig,
|
||||
InstallTemplateArgs,
|
||||
ModelConfig,
|
||||
TemplateDataSource,
|
||||
TemplateFramework,
|
||||
TemplateVectorDB,
|
||||
@@ -24,50 +25,46 @@ import { installTSTemplate } from "./typescript";
|
||||
// eslint-disable-next-line max-params
|
||||
async function generateContextData(
|
||||
framework: TemplateFramework,
|
||||
modelConfig: ModelConfig,
|
||||
packageManager?: PackageManager,
|
||||
openAiKey?: string,
|
||||
vectorDb?: TemplateVectorDB,
|
||||
dataSource?: TemplateDataSource,
|
||||
llamaCloudKey?: string,
|
||||
useLlamaParse?: boolean,
|
||||
) {
|
||||
if (packageManager) {
|
||||
const runGenerate = `${cyan(
|
||||
framework === "fastapi"
|
||||
? "poetry run python app/engine/generate.py"
|
||||
? "poetry run generate"
|
||||
: `${packageManager} run generate`,
|
||||
)}`;
|
||||
const openAiKeyConfigured = openAiKey || process.env["OPENAI_API_KEY"];
|
||||
const llamaCloudKeyConfigured = (dataSource?.config as FileSourceConfig)
|
||||
?.useLlamaParse
|
||||
const modelConfigured = modelConfig.isConfigured();
|
||||
const llamaCloudKeyConfigured = useLlamaParse
|
||||
? llamaCloudKey || process.env["LLAMA_CLOUD_API_KEY"]
|
||||
: true;
|
||||
const hasVectorDb = vectorDb && vectorDb !== "none";
|
||||
if (framework === "fastapi") {
|
||||
if (
|
||||
openAiKeyConfigured &&
|
||||
llamaCloudKeyConfigured &&
|
||||
!hasVectorDb &&
|
||||
isHavingPoetryLockFile()
|
||||
) {
|
||||
console.log(`Running ${runGenerate} to generate the context data.`);
|
||||
const result = tryPoetryRun("python app/engine/generate.py");
|
||||
if (!result) {
|
||||
console.log(`Failed to run ${runGenerate}.`);
|
||||
process.exit(1);
|
||||
if (modelConfigured && llamaCloudKeyConfigured && !hasVectorDb) {
|
||||
// If all the required environment variables are set, run the generate script
|
||||
if (framework === "fastapi") {
|
||||
if (isHavingPoetryLockFile()) {
|
||||
console.log(`Running ${runGenerate} to generate the context data.`);
|
||||
const result = tryPoetryRun("poetry run generate");
|
||||
if (!result) {
|
||||
console.log(`Failed to run ${runGenerate}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Generated context data`);
|
||||
return;
|
||||
}
|
||||
console.log(`Generated context data`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (openAiKeyConfigured && vectorDb === "none") {
|
||||
} else {
|
||||
console.log(`Running ${runGenerate} to generate the context data.`);
|
||||
await callPackageManager(packageManager, true, ["run", "generate"]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// generate the message of what to do to run the generate script manually
|
||||
const settings = [];
|
||||
if (!openAiKeyConfigured) settings.push("your OpenAI key");
|
||||
if (!modelConfigured) settings.push("your model provider API key");
|
||||
if (!llamaCloudKeyConfigured) settings.push("your Llama Cloud key");
|
||||
if (hasVectorDb) settings.push("your Vector DB environment variables");
|
||||
const settingsMessage =
|
||||
@@ -79,38 +76,16 @@ async function generateContextData(
|
||||
|
||||
const copyContextData = async (
|
||||
root: string,
|
||||
dataSource?: TemplateDataSource,
|
||||
dataSources: TemplateDataSource[],
|
||||
) => {
|
||||
const destPath = path.join(root, "data");
|
||||
for (const dataSource of dataSources) {
|
||||
const dataSourceConfig = dataSource?.config as FileSourceConfig;
|
||||
// Copy local data
|
||||
const dataPath = dataSourceConfig.path;
|
||||
|
||||
const dataSourceConfig = dataSource?.config as FileSourceConfig;
|
||||
|
||||
// Copy file
|
||||
if (dataSource?.type === "file") {
|
||||
if (dataSourceConfig.path) {
|
||||
console.log(`\nCopying file to ${cyan(destPath)}\n`);
|
||||
await fs.mkdir(destPath, { recursive: true });
|
||||
await fs.copyFile(
|
||||
dataSourceConfig.path,
|
||||
path.join(destPath, path.basename(dataSourceConfig.path)),
|
||||
);
|
||||
} else {
|
||||
console.log("Missing file path in config");
|
||||
process.exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy folder
|
||||
if (dataSource?.type === "folder") {
|
||||
const srcPath =
|
||||
dataSourceConfig.path ?? path.join(templatesDir, "components", "data");
|
||||
console.log(`\nCopying data to ${cyan(destPath)}\n`);
|
||||
await copy("**", destPath, {
|
||||
parents: true,
|
||||
cwd: srcPath,
|
||||
});
|
||||
return;
|
||||
const destPath = path.join(root, "data", path.basename(dataPath));
|
||||
console.log("Copying data from path:", dataPath);
|
||||
await fsExtra.copy(dataPath, destPath);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -145,45 +120,65 @@ export const installTemplate = async (
|
||||
|
||||
if (props.framework === "fastapi") {
|
||||
await installPythonTemplate(props);
|
||||
// write loaders configuration (currently Python only)
|
||||
await writeLoadersConfig(
|
||||
props.root,
|
||||
props.dataSources,
|
||||
props.useLlamaParse,
|
||||
);
|
||||
} else {
|
||||
await installTSTemplate(props);
|
||||
}
|
||||
|
||||
// write tools configuration
|
||||
await writeToolsConfig(
|
||||
props.root,
|
||||
props.tools,
|
||||
props.framework === "fastapi" ? ConfigFileType.YAML : ConfigFileType.JSON,
|
||||
);
|
||||
|
||||
if (props.backend) {
|
||||
// This is a backend, so we need to copy the test data and create the env file.
|
||||
|
||||
// Copy the environment file to the target directory.
|
||||
await createBackendEnvFile(props.root, {
|
||||
openAiKey: props.openAiKey,
|
||||
modelConfig: props.modelConfig,
|
||||
llamaCloudKey: props.llamaCloudKey,
|
||||
vectorDb: props.vectorDb,
|
||||
model: props.model,
|
||||
embeddingModel: props.embeddingModel,
|
||||
framework: props.framework,
|
||||
dataSource: props.dataSource,
|
||||
dataSources: props.dataSources,
|
||||
port: props.externalPort,
|
||||
tools: props.tools,
|
||||
});
|
||||
|
||||
if (props.engine === "context") {
|
||||
await copyContextData(props.root, props.dataSource);
|
||||
if (props.dataSources.length > 0) {
|
||||
console.log("\nGenerating context data...\n");
|
||||
await copyContextData(
|
||||
props.root,
|
||||
props.dataSources.filter((ds) => ds.type === "file"),
|
||||
);
|
||||
if (
|
||||
props.postInstallAction === "runApp" ||
|
||||
props.postInstallAction === "dependencies"
|
||||
) {
|
||||
await generateContextData(
|
||||
props.framework,
|
||||
props.modelConfig,
|
||||
props.packageManager,
|
||||
props.openAiKey,
|
||||
props.vectorDb,
|
||||
props.dataSource,
|
||||
props.llamaCloudKey,
|
||||
props.useLlamaParse,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create tool-output directory
|
||||
if (props.tools && props.tools.length > 0) {
|
||||
await fsExtra.mkdir(path.join(props.root, "tool-output"));
|
||||
}
|
||||
} else {
|
||||
// this is a frontend for a full-stack app, create .env file with model information
|
||||
createFrontendEnvFile(props.root, {
|
||||
model: props.model,
|
||||
await createFrontendEnvFile(props.root, {
|
||||
customApiPath: props.customApiPath,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import ciInfo from "ci-info";
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams } from ".";
|
||||
import { questionHandlers, toChoice } from "../../questions";
|
||||
|
||||
const MODELS = [
|
||||
"claude-3-opus",
|
||||
"claude-3-sonnet",
|
||||
"claude-3-haiku",
|
||||
"claude-2.1",
|
||||
"claude-instant-1.2",
|
||||
];
|
||||
const DEFAULT_MODEL = MODELS[0];
|
||||
|
||||
// TODO: get embedding vector dimensions from the anthropic sdk (currently not supported)
|
||||
// Use huggingface embedding models for now
|
||||
enum HuggingFaceEmbeddingModelType {
|
||||
XENOVA_ALL_MINILM_L6_V2 = "all-MiniLM-L6-v2",
|
||||
XENOVA_ALL_MPNET_BASE_V2 = "all-mpnet-base-v2",
|
||||
}
|
||||
type ModelData = {
|
||||
dimensions: number;
|
||||
};
|
||||
const EMBEDDING_MODELS: Record<HuggingFaceEmbeddingModelType, ModelData> = {
|
||||
[HuggingFaceEmbeddingModelType.XENOVA_ALL_MINILM_L6_V2]: {
|
||||
dimensions: 384,
|
||||
},
|
||||
[HuggingFaceEmbeddingModelType.XENOVA_ALL_MPNET_BASE_V2]: {
|
||||
dimensions: 768,
|
||||
},
|
||||
};
|
||||
const DEFAULT_EMBEDDING_MODEL = Object.keys(EMBEDDING_MODELS)[0];
|
||||
const DEFAULT_DIMENSIONS = Object.values(EMBEDDING_MODELS)[0].dimensions;
|
||||
|
||||
type AnthropicQuestionsParams = {
|
||||
apiKey?: string;
|
||||
askModels: boolean;
|
||||
};
|
||||
|
||||
export async function askAnthropicQuestions({
|
||||
askModels,
|
||||
apiKey,
|
||||
}: AnthropicQuestionsParams): Promise<ModelConfigParams> {
|
||||
const config: ModelConfigParams = {
|
||||
apiKey,
|
||||
model: DEFAULT_MODEL,
|
||||
embeddingModel: DEFAULT_EMBEDDING_MODEL,
|
||||
dimensions: DEFAULT_DIMENSIONS,
|
||||
isConfigured(): boolean {
|
||||
if (config.apiKey) {
|
||||
return true;
|
||||
}
|
||||
if (process.env["ANTHROPIC_API_KEY"]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
if (!config.apiKey) {
|
||||
const { key } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "key",
|
||||
message:
|
||||
"Please provide your Anthropic API key (or leave blank to use ANTHROPIC_API_KEY env variable):",
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
config.apiKey = key || process.env.ANTHROPIC_API_KEY;
|
||||
}
|
||||
|
||||
// use default model values in CI or if user should not be asked
|
||||
const useDefaults = ciInfo.isCI || !askModels;
|
||||
if (!useDefaults) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "model",
|
||||
message: "Which LLM model would you like to use?",
|
||||
choices: MODELS.map(toChoice),
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
config.model = model;
|
||||
|
||||
const { embeddingModel } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "embeddingModel",
|
||||
message: "Which embedding model would you like to use?",
|
||||
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
config.embeddingModel = embeddingModel;
|
||||
config.dimensions =
|
||||
EMBEDDING_MODELS[
|
||||
embeddingModel as HuggingFaceEmbeddingModelType
|
||||
].dimensions;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import ciInfo from "ci-info";
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams } from ".";
|
||||
import { questionHandlers, toChoice } from "../../questions";
|
||||
|
||||
const MODELS = ["gemini-1.5-pro-latest", "gemini-pro", "gemini-pro-vision"];
|
||||
type ModelData = {
|
||||
dimensions: number;
|
||||
};
|
||||
const EMBEDDING_MODELS: Record<string, ModelData> = {
|
||||
"embedding-001": { dimensions: 768 },
|
||||
"text-embedding-004": { dimensions: 768 },
|
||||
};
|
||||
|
||||
const DEFAULT_MODEL = MODELS[0];
|
||||
const DEFAULT_EMBEDDING_MODEL = Object.keys(EMBEDDING_MODELS)[0];
|
||||
const DEFAULT_DIMENSIONS = Object.values(EMBEDDING_MODELS)[0].dimensions;
|
||||
|
||||
type GeminiQuestionsParams = {
|
||||
apiKey?: string;
|
||||
askModels: boolean;
|
||||
};
|
||||
|
||||
export async function askGeminiQuestions({
|
||||
askModels,
|
||||
apiKey,
|
||||
}: GeminiQuestionsParams): Promise<ModelConfigParams> {
|
||||
const config: ModelConfigParams = {
|
||||
apiKey,
|
||||
model: DEFAULT_MODEL,
|
||||
embeddingModel: DEFAULT_EMBEDDING_MODEL,
|
||||
dimensions: DEFAULT_DIMENSIONS,
|
||||
isConfigured(): boolean {
|
||||
if (config.apiKey) {
|
||||
return true;
|
||||
}
|
||||
if (process.env["GOOGLE_API_KEY"]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
if (!config.apiKey) {
|
||||
const { key } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "key",
|
||||
message:
|
||||
"Please provide your Google API key (or leave blank to use GOOGLE_API_KEY env variable):",
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
config.apiKey = key || process.env.GOOGLE_API_KEY;
|
||||
}
|
||||
|
||||
// use default model values in CI or if user should not be asked
|
||||
const useDefaults = ciInfo.isCI || !askModels;
|
||||
if (!useDefaults) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "model",
|
||||
message: "Which LLM model would you like to use?",
|
||||
choices: MODELS.map(toChoice),
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
config.model = model;
|
||||
|
||||
const { embeddingModel } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "embeddingModel",
|
||||
message: "Which embedding model would you like to use?",
|
||||
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
config.embeddingModel = embeddingModel;
|
||||
config.dimensions = EMBEDDING_MODELS[embeddingModel].dimensions;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import ciInfo from "ci-info";
|
||||
import prompts from "prompts";
|
||||
import { questionHandlers } from "../../questions";
|
||||
import { ModelConfig, ModelProvider } from "../types";
|
||||
import { askAnthropicQuestions } from "./anthropic";
|
||||
import { askGeminiQuestions } from "./gemini";
|
||||
import { askOllamaQuestions } from "./ollama";
|
||||
import { askOpenAIQuestions } from "./openai";
|
||||
|
||||
const DEFAULT_MODEL_PROVIDER = "openai";
|
||||
|
||||
export type ModelConfigQuestionsParams = {
|
||||
openAiKey?: string;
|
||||
askModels: boolean;
|
||||
};
|
||||
|
||||
export type ModelConfigParams = Omit<ModelConfig, "provider">;
|
||||
|
||||
export async function askModelConfig({
|
||||
askModels,
|
||||
openAiKey,
|
||||
}: ModelConfigQuestionsParams): Promise<ModelConfig> {
|
||||
let modelProvider: ModelProvider = DEFAULT_MODEL_PROVIDER;
|
||||
if (askModels && !ciInfo.isCI) {
|
||||
const { provider } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "provider",
|
||||
message: "Which model provider would you like to use",
|
||||
choices: [
|
||||
{
|
||||
title: "OpenAI",
|
||||
value: "openai",
|
||||
},
|
||||
{ title: "Ollama", value: "ollama" },
|
||||
{ title: "Anthropic", value: "anthropic" },
|
||||
{ title: "Gemini", value: "gemini" },
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
modelProvider = provider;
|
||||
}
|
||||
|
||||
let modelConfig: ModelConfigParams;
|
||||
switch (modelProvider) {
|
||||
case "ollama":
|
||||
modelConfig = await askOllamaQuestions({ askModels });
|
||||
break;
|
||||
case "anthropic":
|
||||
modelConfig = await askAnthropicQuestions({ askModels });
|
||||
break;
|
||||
case "gemini":
|
||||
modelConfig = await askGeminiQuestions({ askModels });
|
||||
break;
|
||||
default:
|
||||
modelConfig = await askOpenAIQuestions({
|
||||
openAiKey,
|
||||
askModels,
|
||||
});
|
||||
}
|
||||
return {
|
||||
...modelConfig,
|
||||
provider: modelProvider,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import ciInfo from "ci-info";
|
||||
import ollama, { type ModelResponse } from "ollama";
|
||||
import { red } from "picocolors";
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams } from ".";
|
||||
import { questionHandlers, toChoice } from "../../questions";
|
||||
|
||||
type ModelData = {
|
||||
dimensions: number;
|
||||
};
|
||||
const MODELS = ["llama3:8b", "wizardlm2:7b", "gemma:7b", "phi3"];
|
||||
const DEFAULT_MODEL = MODELS[0];
|
||||
// TODO: get embedding vector dimensions from the ollama sdk (currently not supported)
|
||||
const EMBEDDING_MODELS: Record<string, ModelData> = {
|
||||
"nomic-embed-text": { dimensions: 768 },
|
||||
"mxbai-embed-large": { dimensions: 1024 },
|
||||
"all-minilm": { dimensions: 384 },
|
||||
};
|
||||
const DEFAULT_EMBEDDING_MODEL: string = Object.keys(EMBEDDING_MODELS)[0];
|
||||
|
||||
type OllamaQuestionsParams = {
|
||||
askModels: boolean;
|
||||
};
|
||||
|
||||
export async function askOllamaQuestions({
|
||||
askModels,
|
||||
}: OllamaQuestionsParams): Promise<ModelConfigParams> {
|
||||
const config: ModelConfigParams = {
|
||||
model: DEFAULT_MODEL,
|
||||
embeddingModel: DEFAULT_EMBEDDING_MODEL,
|
||||
dimensions: EMBEDDING_MODELS[DEFAULT_EMBEDDING_MODEL].dimensions,
|
||||
isConfigured(): boolean {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
// use default model values in CI or if user should not be asked
|
||||
const useDefaults = ciInfo.isCI || !askModels;
|
||||
if (!useDefaults) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "model",
|
||||
message: "Which LLM model would you like to use?",
|
||||
choices: MODELS.map(toChoice),
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
await ensureModel(model);
|
||||
config.model = model;
|
||||
|
||||
const { embeddingModel } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "embeddingModel",
|
||||
message: "Which embedding model would you like to use?",
|
||||
choices: Object.keys(EMBEDDING_MODELS).map(toChoice),
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
await ensureModel(embeddingModel);
|
||||
config.embeddingModel = embeddingModel;
|
||||
config.dimensions = EMBEDDING_MODELS[embeddingModel].dimensions;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
async function ensureModel(modelName: string) {
|
||||
try {
|
||||
if (modelName.split(":").length === 1) {
|
||||
// model doesn't have a version suffix, use latest
|
||||
modelName = modelName + ":latest";
|
||||
}
|
||||
const { models } = await ollama.list();
|
||||
const found =
|
||||
models.find((model: ModelResponse) => model.name === modelName) !==
|
||||
undefined;
|
||||
if (!found) {
|
||||
console.log(
|
||||
red(
|
||||
`Model ${modelName} was not pulled yet. Call 'ollama pull ${modelName}' and try again.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
red("Listing Ollama models failed. Is 'ollama' running? " + error),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import ciInfo from "ci-info";
|
||||
import got from "got";
|
||||
import ora from "ora";
|
||||
import { red } from "picocolors";
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams, ModelConfigQuestionsParams } from ".";
|
||||
import { questionHandlers } from "../../questions";
|
||||
|
||||
const OPENAI_API_URL = "https://api.openai.com/v1";
|
||||
|
||||
const DEFAULT_MODEL = "gpt-3.5-turbo";
|
||||
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-large";
|
||||
|
||||
export async function askOpenAIQuestions({
|
||||
openAiKey,
|
||||
askModels,
|
||||
}: ModelConfigQuestionsParams): Promise<ModelConfigParams> {
|
||||
const config: ModelConfigParams = {
|
||||
apiKey: openAiKey,
|
||||
model: DEFAULT_MODEL,
|
||||
embeddingModel: DEFAULT_EMBEDDING_MODEL,
|
||||
dimensions: getDimensions(DEFAULT_EMBEDDING_MODEL),
|
||||
isConfigured(): boolean {
|
||||
if (config.apiKey) {
|
||||
return true;
|
||||
}
|
||||
if (process.env["OPENAI_API_KEY"]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
if (!config.apiKey) {
|
||||
const { key } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "key",
|
||||
message: askModels
|
||||
? "Please provide your OpenAI API key (or leave blank to use OPENAI_API_KEY env variable):"
|
||||
: "Please provide your OpenAI API key (leave blank to skip):",
|
||||
validate: (value: string) => {
|
||||
if (askModels && !value) {
|
||||
if (process.env.OPENAI_API_KEY) {
|
||||
return true;
|
||||
}
|
||||
return "OPENAI_API_KEY env variable is not set - key is required";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
config.apiKey = key || process.env.OPENAI_API_KEY;
|
||||
}
|
||||
|
||||
// use default model values in CI or if user should not be asked
|
||||
const useDefaults = ciInfo.isCI || !askModels;
|
||||
if (!useDefaults) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "model",
|
||||
message: "Which LLM model would you like to use?",
|
||||
choices: await getAvailableModelChoices(false, config.apiKey),
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
config.model = model;
|
||||
|
||||
const { embeddingModel } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "embeddingModel",
|
||||
message: "Which embedding model would you like to use?",
|
||||
choices: await getAvailableModelChoices(true, config.apiKey),
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
config.embeddingModel = embeddingModel;
|
||||
config.dimensions = getDimensions(embeddingModel);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
async function getAvailableModelChoices(
|
||||
selectEmbedding: boolean,
|
||||
apiKey?: string,
|
||||
) {
|
||||
if (!apiKey) {
|
||||
throw new Error("need OpenAI key to retrieve model choices");
|
||||
}
|
||||
const isLLMModel = (modelId: string) => {
|
||||
return modelId.startsWith("gpt");
|
||||
};
|
||||
|
||||
const isEmbeddingModel = (modelId: string) => {
|
||||
return modelId.includes("embedding");
|
||||
};
|
||||
|
||||
const spinner = ora("Fetching available models").start();
|
||||
try {
|
||||
const response = await got(`${OPENAI_API_URL}/models`, {
|
||||
headers: {
|
||||
Authorization: "Bearer " + apiKey,
|
||||
},
|
||||
timeout: 5000,
|
||||
responseType: "json",
|
||||
});
|
||||
const data: any = await response.body;
|
||||
spinner.stop();
|
||||
return data.data
|
||||
.filter((model: any) =>
|
||||
selectEmbedding ? isEmbeddingModel(model.id) : isLLMModel(model.id),
|
||||
)
|
||||
.map((el: any) => {
|
||||
return {
|
||||
title: el.id,
|
||||
value: el.id,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
spinner.stop();
|
||||
if ((error as any).response?.statusCode === 401) {
|
||||
console.log(
|
||||
red(
|
||||
"Invalid OpenAI API key provided! Please provide a valid key and try again!",
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.log(red("Request failed: " + error));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getDimensions(modelName: string) {
|
||||
// at 2024-04-24 all OpenAI embedding models support 1536 dimensions except
|
||||
// "text-embedding-3-large", see https://openai.com/blog/new-embedding-models-and-api-updates
|
||||
return modelName === "text-embedding-3-large" ? 1024 : 1536;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* Function to conditionally load the global-agent/bootstrap module */
|
||||
export async function initializeGlobalAgent() {
|
||||
if (process.env.GLOBAL_AGENT_HTTP_PROXY) {
|
||||
/* Dynamically import global-agent/bootstrap */
|
||||
await import("global-agent/bootstrap");
|
||||
console.log("Proxy enabled via global-agent.");
|
||||
}
|
||||
}
|
||||
+171
-85
@@ -3,13 +3,14 @@ import path from "path";
|
||||
import { cyan, red } from "picocolors";
|
||||
import { parse, stringify } from "smol-toml";
|
||||
import terminalLink from "terminal-link";
|
||||
import { copy } from "./copy";
|
||||
|
||||
import { assetRelocator, copy } from "./copy";
|
||||
import { templatesDir } from "./dir";
|
||||
import { isPoetryAvailable, tryPoetryInstall } from "./poetry";
|
||||
import { Tool } from "./tools";
|
||||
import {
|
||||
FileSourceConfig,
|
||||
InstallTemplateArgs,
|
||||
ModelConfig,
|
||||
TemplateDataSource,
|
||||
TemplateVectorDB,
|
||||
} from "./types";
|
||||
@@ -21,8 +22,9 @@ interface Dependency {
|
||||
}
|
||||
|
||||
const getAdditionalDependencies = (
|
||||
modelConfig: ModelConfig,
|
||||
vectorDb?: TemplateVectorDB,
|
||||
dataSource?: TemplateDataSource,
|
||||
dataSources?: TemplateDataSource[],
|
||||
tools?: Tool[],
|
||||
) => {
|
||||
const dependencies: Dependency[] = [];
|
||||
@@ -41,6 +43,7 @@ const getAdditionalDependencies = (
|
||||
name: "llama-index-vector-stores-postgres",
|
||||
version: "^0.1.1",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "pinecone": {
|
||||
dependencies.push({
|
||||
@@ -49,30 +52,123 @@ const getAdditionalDependencies = (
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "milvus": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-milvus",
|
||||
version: "^0.1.6",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "pymilvus",
|
||||
version: "2.3.7",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "astra": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-astra-db",
|
||||
version: "^0.1.5",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "qdrant": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-qdrant",
|
||||
version: "^0.2.8",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "chroma": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-chroma",
|
||||
version: "^0.1.8",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add data source dependencies
|
||||
const dataSourceType = dataSource?.type;
|
||||
if (dataSourceType === "file" || dataSourceType === "folder") {
|
||||
// llama-index-readers-file (pdf, excel, csv) is already included in llama_index package
|
||||
dependencies.push({
|
||||
name: "docx2txt",
|
||||
version: "^0.8",
|
||||
});
|
||||
} else if (dataSourceType === "web") {
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-web",
|
||||
version: "^0.1.6",
|
||||
});
|
||||
if (dataSources) {
|
||||
for (const ds of dataSources) {
|
||||
const dsType = ds?.type;
|
||||
switch (dsType) {
|
||||
case "file":
|
||||
dependencies.push({
|
||||
name: "docx2txt",
|
||||
version: "^0.8",
|
||||
});
|
||||
break;
|
||||
case "web":
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-web",
|
||||
version: "^0.1.6",
|
||||
});
|
||||
break;
|
||||
case "db":
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-database",
|
||||
version: "^0.1.3",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "pymysql",
|
||||
version: "^1.1.0",
|
||||
extras: ["rsa"],
|
||||
});
|
||||
dependencies.push({
|
||||
name: "psycopg2",
|
||||
version: "^2.9.9",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add tools dependencies
|
||||
console.log("Adding tools dependencies");
|
||||
tools?.forEach((tool) => {
|
||||
tool.dependencies?.forEach((dep) => {
|
||||
dependencies.push(dep);
|
||||
});
|
||||
});
|
||||
|
||||
switch (modelConfig.provider) {
|
||||
case "ollama":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-ollama",
|
||||
version: "0.1.2",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-ollama",
|
||||
version: "0.1.2",
|
||||
});
|
||||
break;
|
||||
case "openai":
|
||||
dependencies.push({
|
||||
name: "llama-index-agent-openai",
|
||||
version: "0.2.2",
|
||||
});
|
||||
break;
|
||||
case "anthropic":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-anthropic",
|
||||
version: "0.1.10",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-huggingface",
|
||||
version: "0.2.0",
|
||||
});
|
||||
break;
|
||||
case "gemini":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-gemini",
|
||||
version: "0.1.7",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-gemini",
|
||||
version: "0.1.6",
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
@@ -166,107 +262,97 @@ export const installPythonTemplate = async ({
|
||||
root,
|
||||
template,
|
||||
framework,
|
||||
engine,
|
||||
vectorDb,
|
||||
dataSource,
|
||||
dataSources,
|
||||
tools,
|
||||
postInstallAction,
|
||||
observability,
|
||||
modelConfig,
|
||||
}: Pick<
|
||||
InstallTemplateArgs,
|
||||
| "root"
|
||||
| "framework"
|
||||
| "template"
|
||||
| "engine"
|
||||
| "vectorDb"
|
||||
| "dataSource"
|
||||
| "dataSources"
|
||||
| "tools"
|
||||
| "postInstallAction"
|
||||
| "observability"
|
||||
| "modelConfig"
|
||||
>) => {
|
||||
console.log("\nInitializing Python project with template:", template, "\n");
|
||||
const templatePath = path.join(templatesDir, "types", template, framework);
|
||||
await copy("**", root, {
|
||||
parents: true,
|
||||
cwd: templatePath,
|
||||
rename(name) {
|
||||
switch (name) {
|
||||
case "gitignore": {
|
||||
return `.${name}`;
|
||||
}
|
||||
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
|
||||
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
|
||||
case "README-template.md": {
|
||||
return "README.md";
|
||||
}
|
||||
default: {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
},
|
||||
rename: assetRelocator,
|
||||
});
|
||||
|
||||
if (engine === "context") {
|
||||
const enginePath = path.join(root, "app", "engine");
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
const enginePath = path.join(root, "app", "engine");
|
||||
|
||||
const vectorDbDirName = vectorDb ?? "none";
|
||||
const VectorDBPath = path.join(
|
||||
compPath,
|
||||
"vectordbs",
|
||||
"python",
|
||||
vectorDbDirName,
|
||||
);
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: VectorDBPath,
|
||||
});
|
||||
// Copy selected vector DB
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "vectordbs", "python", vectorDb ?? "none"),
|
||||
});
|
||||
|
||||
// Copy engine code
|
||||
if (tools !== undefined && tools.length > 0) {
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "python", "agent"),
|
||||
});
|
||||
// Write tools_config.json
|
||||
const configContent: Record<string, any> = {};
|
||||
tools.forEach((tool) => {
|
||||
configContent[tool.name] = tool.config ?? {};
|
||||
});
|
||||
const configFilePath = path.join(root, "tools_config.json");
|
||||
await fs.writeFile(
|
||||
configFilePath,
|
||||
JSON.stringify(configContent, null, 2),
|
||||
);
|
||||
} else {
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "python", "chat"),
|
||||
});
|
||||
}
|
||||
// Copy all loaders to enginePath
|
||||
const loaderPath = path.join(enginePath, "loaders");
|
||||
await copy("**", loaderPath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "loaders", "python"),
|
||||
});
|
||||
|
||||
const dataSourceType = dataSource?.type;
|
||||
if (dataSourceType !== undefined && dataSourceType !== "none") {
|
||||
let loaderFolder: string;
|
||||
if (dataSourceType === "file" || dataSourceType === "folder") {
|
||||
const dataSourceConfig = dataSource?.config as FileSourceConfig;
|
||||
loaderFolder = dataSourceConfig.useLlamaParse ? "llama_parse" : "file";
|
||||
} else {
|
||||
loaderFolder = dataSourceType;
|
||||
}
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "loaders", "python", loaderFolder),
|
||||
});
|
||||
}
|
||||
// Select and copy engine code based on data sources and tools
|
||||
let engine;
|
||||
tools = tools ?? [];
|
||||
if (dataSources.length > 0 && tools.length === 0) {
|
||||
console.log("\nNo tools selected - use optimized context chat engine\n");
|
||||
engine = "chat";
|
||||
} else {
|
||||
engine = "agent";
|
||||
}
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "python", engine),
|
||||
});
|
||||
|
||||
console.log("Adding additional dependencies");
|
||||
|
||||
const addOnDependencies = getAdditionalDependencies(
|
||||
modelConfig,
|
||||
vectorDb,
|
||||
dataSource,
|
||||
dataSources,
|
||||
tools,
|
||||
);
|
||||
|
||||
if (observability === "opentelemetry") {
|
||||
addOnDependencies.push({
|
||||
name: "traceloop-sdk",
|
||||
version: "^0.15.11",
|
||||
});
|
||||
|
||||
const templateObservabilityPath = path.join(
|
||||
templatesDir,
|
||||
"components",
|
||||
"observability",
|
||||
"python",
|
||||
"opentelemetry",
|
||||
);
|
||||
await copy("**", path.join(root, "app"), {
|
||||
cwd: templateObservabilityPath,
|
||||
});
|
||||
}
|
||||
|
||||
await addDependencies(root, addOnDependencies);
|
||||
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
installPythonDependencies();
|
||||
}
|
||||
|
||||
// Copy deployment files for python
|
||||
await copy("**", root, {
|
||||
cwd: path.join(compPath, "deployments", "python"),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { red } from "picocolors";
|
||||
import yaml from "yaml";
|
||||
import { EnvVar } from "./env-variables";
|
||||
import { makeDir } from "./make-dir";
|
||||
import { TemplateFramework } from "./types";
|
||||
|
||||
export const TOOL_SYSTEM_PROMPT_ENV_VAR = "TOOL_SYSTEM_PROMPT";
|
||||
|
||||
export enum ToolType {
|
||||
LLAMAHUB = "llamahub",
|
||||
LOCAL = "local",
|
||||
}
|
||||
|
||||
export type Tool = {
|
||||
display: string;
|
||||
name: string;
|
||||
config?: Record<string, any>;
|
||||
dependencies?: ToolDependencies[];
|
||||
supportedFrameworks?: Array<TemplateFramework>;
|
||||
type: ToolType;
|
||||
envVars?: EnvVar[];
|
||||
};
|
||||
|
||||
export type ToolDependencies = {
|
||||
name: string;
|
||||
version?: string;
|
||||
@@ -27,6 +44,15 @@ export const supportedTools: Tool[] = [
|
||||
version: "0.1.2",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi"],
|
||||
type: ToolType.LLAMAHUB,
|
||||
envVars: [
|
||||
{
|
||||
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
|
||||
description: "System prompt for google search tool.",
|
||||
value: `You are a Google search agent. You help users to get information from Google search.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
display: "Wikipedia",
|
||||
@@ -37,6 +63,59 @@ export const supportedTools: Tool[] = [
|
||||
version: "0.1.2",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi", "express", "nextjs"],
|
||||
type: ToolType.LLAMAHUB,
|
||||
envVars: [
|
||||
{
|
||||
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
|
||||
description: "System prompt for wiki tool.",
|
||||
value: `You are a Wikipedia agent. You help users to get information from Wikipedia.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
display: "Weather",
|
||||
name: "weather",
|
||||
dependencies: [],
|
||||
supportedFrameworks: ["fastapi", "express", "nextjs"],
|
||||
type: ToolType.LOCAL,
|
||||
envVars: [
|
||||
{
|
||||
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
|
||||
description: "System prompt for weather tool.",
|
||||
value: `You are a weather forecast agent. You help users to get the weather forecast for a given location.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
display: "Code Interpreter",
|
||||
name: "interpreter",
|
||||
dependencies: [
|
||||
{
|
||||
name: "e2b_code_interpreter",
|
||||
version: "0.0.7",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi", "express", "nextjs"],
|
||||
type: ToolType.LOCAL,
|
||||
envVars: [
|
||||
{
|
||||
name: "E2B_API_KEY",
|
||||
description:
|
||||
"E2B_API_KEY key is required to run code interpreter tool. Get it here: https://e2b.dev/docs/getting-started/api-key",
|
||||
},
|
||||
{
|
||||
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
|
||||
description: "System prompt for code interpreter tool.",
|
||||
value: `You are a Python interpreter.
|
||||
- You are given tasks to complete and you run python code to solve them.
|
||||
- The python code runs in a Jupyter notebook. Every time you call \`interpreter\` tool, the python code is executed in a separate cell. It's okay to make multiple calls to \`interpreter\`.
|
||||
- Display visualizations using matplotlib or any other visualization library directly in the notebook. Shouldn't save the visualizations to a file, just return the base64 encoded data.
|
||||
- You can install any pip package (if it exists) if you need to but the usual packages for data analysis are already preinstalled.
|
||||
- You can run any python code you want in a secure environment.
|
||||
- Use absolute url from result to display images or any other media.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -69,3 +148,43 @@ export const toolsRequireConfig = (tools?: Tool[]): boolean => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export enum ConfigFileType {
|
||||
YAML = "yaml",
|
||||
JSON = "json",
|
||||
}
|
||||
|
||||
export const writeToolsConfig = async (
|
||||
root: string,
|
||||
tools: Tool[] = [],
|
||||
type: ConfigFileType = ConfigFileType.YAML,
|
||||
) => {
|
||||
if (tools.length === 0) return; // no tools selected, no config need
|
||||
const configContent: {
|
||||
[key in ToolType]: Record<string, any>;
|
||||
} = {
|
||||
local: {},
|
||||
llamahub: {},
|
||||
};
|
||||
tools.forEach((tool) => {
|
||||
if (tool.type === ToolType.LLAMAHUB) {
|
||||
configContent.llamahub[tool.name] = tool.config ?? {};
|
||||
}
|
||||
if (tool.type === ToolType.LOCAL) {
|
||||
configContent.local[tool.name] = tool.config ?? {};
|
||||
}
|
||||
});
|
||||
const configPath = path.join(root, "config");
|
||||
await makeDir(configPath);
|
||||
if (type === ConfigFileType.YAML) {
|
||||
await fs.writeFile(
|
||||
path.join(configPath, "tools.yaml"),
|
||||
yaml.stringify(configContent),
|
||||
);
|
||||
} else {
|
||||
await fs.writeFile(
|
||||
path.join(configPath, "tools.json"),
|
||||
JSON.stringify(configContent, null, 2),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+34
-13
@@ -1,11 +1,27 @@
|
||||
import { PackageManager } from "../helpers/get-pkg-manager";
|
||||
import { Tool } from "./tools";
|
||||
|
||||
export type TemplateType = "simple" | "streaming" | "community" | "llamapack";
|
||||
export type ModelProvider = "openai" | "ollama" | "anthropic" | "gemini";
|
||||
export type ModelConfig = {
|
||||
provider: ModelProvider;
|
||||
apiKey?: string;
|
||||
model: string;
|
||||
embeddingModel: string;
|
||||
dimensions: number;
|
||||
isConfigured(): boolean;
|
||||
};
|
||||
export type TemplateType = "streaming" | "community" | "llamapack";
|
||||
export type TemplateFramework = "nextjs" | "express" | "fastapi";
|
||||
export type TemplateEngine = "simple" | "context";
|
||||
export type TemplateUI = "html" | "shadcn";
|
||||
export type TemplateVectorDB = "none" | "mongo" | "pg" | "pinecone";
|
||||
export type TemplateVectorDB =
|
||||
| "none"
|
||||
| "mongo"
|
||||
| "pg"
|
||||
| "pinecone"
|
||||
| "milvus"
|
||||
| "astra"
|
||||
| "qdrant"
|
||||
| "chroma";
|
||||
export type TemplatePostInstallAction =
|
||||
| "none"
|
||||
| "VSCode"
|
||||
@@ -15,18 +31,26 @@ export type TemplateDataSource = {
|
||||
type: TemplateDataSourceType;
|
||||
config: TemplateDataSourceConfig;
|
||||
};
|
||||
export type TemplateDataSourceType = "none" | "file" | "folder" | "web";
|
||||
export type TemplateDataSourceType = "file" | "web" | "db";
|
||||
export type TemplateObservability = "none" | "opentelemetry";
|
||||
// Config for both file and folder
|
||||
export type FileSourceConfig = {
|
||||
path?: string;
|
||||
useLlamaParse?: boolean;
|
||||
path: string;
|
||||
};
|
||||
export type WebSourceConfig = {
|
||||
baseUrl?: string;
|
||||
prefix?: string;
|
||||
depth?: number;
|
||||
};
|
||||
export type TemplateDataSourceConfig = FileSourceConfig | WebSourceConfig;
|
||||
export type DbSourceConfig = {
|
||||
uri?: string;
|
||||
queries?: string;
|
||||
};
|
||||
|
||||
export type TemplateDataSourceConfig =
|
||||
| FileSourceConfig
|
||||
| WebSourceConfig
|
||||
| DbSourceConfig;
|
||||
|
||||
export type CommunityProjectConfig = {
|
||||
owner: string;
|
||||
@@ -42,15 +66,12 @@ export interface InstallTemplateArgs {
|
||||
isOnline: boolean;
|
||||
template: TemplateType;
|
||||
framework: TemplateFramework;
|
||||
engine: TemplateEngine;
|
||||
ui: TemplateUI;
|
||||
dataSource?: TemplateDataSource;
|
||||
eslint: boolean;
|
||||
dataSources: TemplateDataSource[];
|
||||
customApiPath?: string;
|
||||
openAiKey?: string;
|
||||
modelConfig: ModelConfig;
|
||||
llamaCloudKey?: string;
|
||||
model: string;
|
||||
embeddingModel: string;
|
||||
useLlamaParse?: boolean;
|
||||
communityProjectConfig?: CommunityProjectConfig;
|
||||
llamapack?: string;
|
||||
vectorDb?: TemplateVectorDB;
|
||||
|
||||
+124
-108
@@ -2,50 +2,12 @@ import fs from "fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { bold, cyan } from "picocolors";
|
||||
import { copy } from "../helpers/copy";
|
||||
import { assetRelocator, copy } from "../helpers/copy";
|
||||
import { callPackageManager } from "../helpers/install";
|
||||
import { templatesDir } from "./dir";
|
||||
import { PackageManager } from "./get-pkg-manager";
|
||||
import { InstallTemplateArgs } from "./types";
|
||||
|
||||
const rename = (name: string) => {
|
||||
switch (name) {
|
||||
case "gitignore":
|
||||
case "eslintrc.json": {
|
||||
return `.${name}`;
|
||||
}
|
||||
// README.md is ignored by webpack-asset-relocator-loader used by ncc:
|
||||
// https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
|
||||
case "README-template.md": {
|
||||
return "README.md";
|
||||
}
|
||||
default: {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const installTSDependencies = async (
|
||||
packageJson: any,
|
||||
packageManager: PackageManager,
|
||||
isOnline: boolean,
|
||||
): Promise<void> => {
|
||||
console.log("\nInstalling dependencies:");
|
||||
for (const dependency in packageJson.dependencies)
|
||||
console.log(`- ${cyan(dependency)}`);
|
||||
|
||||
console.log("\nInstalling devDependencies:");
|
||||
for (const dependency in packageJson.devDependencies)
|
||||
console.log(`- ${cyan(dependency)}`);
|
||||
|
||||
console.log();
|
||||
|
||||
await callPackageManager(packageManager, isOnline).catch((error) => {
|
||||
console.error("Failed to install TS dependencies. Exiting...");
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Install a LlamaIndex internal template to a given `root` directory.
|
||||
*/
|
||||
@@ -56,14 +18,14 @@ export const installTSTemplate = async ({
|
||||
isOnline,
|
||||
template,
|
||||
framework,
|
||||
engine,
|
||||
ui,
|
||||
eslint,
|
||||
customApiPath,
|
||||
vectorDb,
|
||||
postInstallAction,
|
||||
backend,
|
||||
observability,
|
||||
tools,
|
||||
dataSources,
|
||||
useLlamaParse,
|
||||
}: InstallTemplateArgs & { backend: boolean }) => {
|
||||
console.log(bold(`Using ${packageManager}.`));
|
||||
|
||||
@@ -73,31 +35,39 @@ export const installTSTemplate = async ({
|
||||
console.log("\nInitializing project with template:", template, "\n");
|
||||
const templatePath = path.join(templatesDir, "types", template, framework);
|
||||
const copySource = ["**"];
|
||||
if (!eslint) copySource.push("!eslintrc.json");
|
||||
|
||||
await copy(copySource, root, {
|
||||
parents: true,
|
||||
cwd: templatePath,
|
||||
rename,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
|
||||
/**
|
||||
* If next.js is used, update its configuration if necessary
|
||||
*/
|
||||
if (framework === "nextjs") {
|
||||
const nextConfigJsonFile = path.join(root, "next.config.json");
|
||||
const nextConfigJson: any = JSON.parse(
|
||||
await fs.readFile(nextConfigJsonFile, "utf8"),
|
||||
);
|
||||
if (!backend) {
|
||||
// update next.config.json for static site generation
|
||||
const nextConfigJsonFile = path.join(root, "next.config.json");
|
||||
const nextConfigJson: any = JSON.parse(
|
||||
await fs.readFile(nextConfigJsonFile, "utf8"),
|
||||
);
|
||||
nextConfigJson.output = "export";
|
||||
nextConfigJson.images = { unoptimized: true };
|
||||
await fs.writeFile(
|
||||
nextConfigJsonFile,
|
||||
JSON.stringify(nextConfigJson, null, 2) + os.EOL,
|
||||
);
|
||||
console.log("\nUsing static site generation\n");
|
||||
} else {
|
||||
if (vectorDb === "milvus") {
|
||||
nextConfigJson.experimental.serverComponentsExternalPackages =
|
||||
nextConfigJson.experimental.serverComponentsExternalPackages ?? [];
|
||||
nextConfigJson.experimental.serverComponentsExternalPackages.push(
|
||||
"@zilliz/milvus2-sdk-node",
|
||||
);
|
||||
}
|
||||
}
|
||||
await fs.writeFile(
|
||||
nextConfigJsonFile,
|
||||
JSON.stringify(nextConfigJson, null, 2) + os.EOL,
|
||||
);
|
||||
|
||||
const webpackConfigOtelFile = path.join(root, "webpack.config.o11y.mjs");
|
||||
if (observability === "opentelemetry") {
|
||||
@@ -109,6 +79,7 @@ export const installTSTemplate = async ({
|
||||
}
|
||||
}
|
||||
|
||||
// copy observability component
|
||||
if (observability && observability !== "none") {
|
||||
const chosenObservabilityPath = path.join(
|
||||
templatesDir,
|
||||
@@ -126,36 +97,40 @@ export const installTSTemplate = async ({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the selected chat engine files to the target directory and reference it.
|
||||
*/
|
||||
let relativeEngineDestPath;
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
if (engine && (framework === "express" || framework === "nextjs")) {
|
||||
console.log("\nUsing chat engine:", engine, "\n");
|
||||
const relativeEngineDestPath =
|
||||
framework === "nextjs"
|
||||
? path.join("app", "api", "chat")
|
||||
: path.join("src", "controllers");
|
||||
const enginePath = path.join(root, relativeEngineDestPath, "engine");
|
||||
|
||||
let vectorDBFolder: string = engine;
|
||||
// copy vector db component
|
||||
console.log("\nUsing vector DB:", vectorDb ?? "none", "\n");
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "vectordbs", "typescript", vectorDb ?? "none"),
|
||||
});
|
||||
|
||||
if (engine !== "simple" && vectorDb) {
|
||||
console.log("\nUsing vector DB:", vectorDb, "\n");
|
||||
vectorDBFolder = vectorDb;
|
||||
}
|
||||
// copy loader component (TS only supports llama_parse and file for now)
|
||||
const loaderFolder = useLlamaParse ? "llama_parse" : "file";
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "loaders", "typescript", loaderFolder),
|
||||
});
|
||||
|
||||
const VectorDBPath = path.join(
|
||||
compPath,
|
||||
"vectordbs",
|
||||
"typescript",
|
||||
vectorDBFolder,
|
||||
);
|
||||
relativeEngineDestPath =
|
||||
framework === "nextjs"
|
||||
? path.join("app", "api", "chat")
|
||||
: path.join("src", "controllers");
|
||||
await copy("**", path.join(root, relativeEngineDestPath, "engine"), {
|
||||
parents: true,
|
||||
cwd: VectorDBPath,
|
||||
});
|
||||
// Select and copy engine code based on data sources and tools
|
||||
let engine;
|
||||
tools = tools ?? [];
|
||||
if (dataSources.length > 0 && tools.length === 0) {
|
||||
console.log("\nNo tools selected - use optimized context chat engine\n");
|
||||
engine = "chat";
|
||||
} else {
|
||||
engine = "agent";
|
||||
}
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "engines", "typescript", engine),
|
||||
});
|
||||
|
||||
/**
|
||||
* Copy the selected UI files to the target directory and reference it.
|
||||
@@ -170,13 +145,54 @@ export const installTSTemplate = async ({
|
||||
await copy("**", destUiPath, {
|
||||
parents: true,
|
||||
cwd: uiPath,
|
||||
rename,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the package.json scripts.
|
||||
*/
|
||||
/** Modify frontend code to use custom API path */
|
||||
if (framework === "nextjs" && !backend) {
|
||||
console.log(
|
||||
"\nUsing external API for frontend, removing API code and configuration\n",
|
||||
);
|
||||
// remove the default api folder and config folder
|
||||
await fs.rm(path.join(root, "app", "api"), { recursive: true });
|
||||
await fs.rm(path.join(root, "config"), { recursive: true, force: true });
|
||||
}
|
||||
|
||||
const packageJson = await updatePackageJson({
|
||||
root,
|
||||
appName,
|
||||
dataSources,
|
||||
relativeEngineDestPath,
|
||||
framework,
|
||||
ui,
|
||||
observability,
|
||||
});
|
||||
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
await installTSDependencies(packageJson, packageManager, isOnline);
|
||||
}
|
||||
|
||||
// Copy deployment files for typescript
|
||||
await copy("**", root, {
|
||||
cwd: path.join(compPath, "deployments", "typescript"),
|
||||
});
|
||||
};
|
||||
|
||||
async function updatePackageJson({
|
||||
root,
|
||||
appName,
|
||||
dataSources,
|
||||
relativeEngineDestPath,
|
||||
framework,
|
||||
ui,
|
||||
observability,
|
||||
}: Pick<
|
||||
InstallTemplateArgs,
|
||||
"root" | "appName" | "dataSources" | "framework" | "ui" | "observability"
|
||||
> & {
|
||||
relativeEngineDestPath: string;
|
||||
}): Promise<any> {
|
||||
const packageJsonFile = path.join(root, "package.json");
|
||||
const packageJson: any = JSON.parse(
|
||||
await fs.readFile(packageJsonFile, "utf8"),
|
||||
@@ -184,26 +200,15 @@ export const installTSTemplate = async ({
|
||||
packageJson.name = appName;
|
||||
packageJson.version = "0.1.0";
|
||||
|
||||
if (framework === "nextjs" && customApiPath) {
|
||||
console.log(
|
||||
"\nUsing external API with custom API path:",
|
||||
customApiPath,
|
||||
"\n",
|
||||
);
|
||||
// remove the default api folder
|
||||
const apiPath = path.join(root, "app", "api");
|
||||
await fs.rm(apiPath, { recursive: true });
|
||||
// modify the dev script to use the custom api path
|
||||
}
|
||||
|
||||
if (engine === "context" && relativeEngineDestPath) {
|
||||
if (relativeEngineDestPath) {
|
||||
// TODO: move script to {root}/scripts for all frameworks
|
||||
// add generate script if using context engine
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
generate: `node ${path.join(
|
||||
generate: `tsx ${path.join(
|
||||
relativeEngineDestPath,
|
||||
"engine",
|
||||
"generate.mjs",
|
||||
"generate.ts",
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
@@ -243,20 +248,31 @@ export const installTSTemplate = async ({
|
||||
};
|
||||
}
|
||||
|
||||
if (!eslint) {
|
||||
// Remove packages starting with "eslint" from devDependencies
|
||||
packageJson.devDependencies = Object.fromEntries(
|
||||
Object.entries(packageJson.devDependencies).filter(
|
||||
([key]) => !key.startsWith("eslint"),
|
||||
),
|
||||
);
|
||||
}
|
||||
await fs.writeFile(
|
||||
packageJsonFile,
|
||||
JSON.stringify(packageJson, null, 2) + os.EOL,
|
||||
);
|
||||
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
await installTSDependencies(packageJson, packageManager, isOnline);
|
||||
}
|
||||
};
|
||||
return packageJson;
|
||||
}
|
||||
|
||||
async function installTSDependencies(
|
||||
packageJson: any,
|
||||
packageManager: PackageManager,
|
||||
isOnline: boolean,
|
||||
): Promise<void> {
|
||||
console.log("\nInstalling dependencies:");
|
||||
for (const dependency in packageJson.dependencies)
|
||||
console.log(`- ${cyan(dependency)}`);
|
||||
|
||||
console.log("\nInstalling devDependencies:");
|
||||
for (const dependency in packageJson.devDependencies)
|
||||
console.log(`- ${cyan(dependency)}`);
|
||||
|
||||
console.log();
|
||||
|
||||
await callPackageManager(packageManager, isOnline).catch((error) => {
|
||||
console.error("Failed to install TS dependencies. Exiting...");
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { execSync } from "child_process";
|
||||
import Commander from "commander";
|
||||
@@ -10,14 +9,19 @@ import prompts from "prompts";
|
||||
import terminalLink from "terminal-link";
|
||||
import checkForUpdate from "update-check";
|
||||
import { createApp } from "./create-app";
|
||||
import { getDataSources } from "./helpers/datasources";
|
||||
import { getPkgManager } from "./helpers/get-pkg-manager";
|
||||
import { isFolderEmpty } from "./helpers/is-folder-empty";
|
||||
import { initializeGlobalAgent } from "./helpers/proxy";
|
||||
import { runApp } from "./helpers/run-app";
|
||||
import { getTools } from "./helpers/tools";
|
||||
import { validateNpmName } from "./helpers/validate-pkg";
|
||||
import packageJson from "./package.json";
|
||||
import { QuestionArgs, askQuestions, onPromptState } from "./questions";
|
||||
|
||||
// Run the initialization function
|
||||
initializeGlobalAgent();
|
||||
|
||||
let projectPath: string = "";
|
||||
|
||||
const handleSigTerm = () => process.exit(0);
|
||||
@@ -32,13 +36,6 @@ const program = new Commander.Command(packageJson.name)
|
||||
.action((name) => {
|
||||
projectPath = name;
|
||||
})
|
||||
.option(
|
||||
"--eslint",
|
||||
`
|
||||
|
||||
Initialize with eslint config.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--use-npm",
|
||||
`
|
||||
@@ -72,13 +69,6 @@ const program = new Commander.Command(packageJson.name)
|
||||
`
|
||||
|
||||
Select a template to bootstrap the application with.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--engine <engine>",
|
||||
`
|
||||
|
||||
Select a chat engine to bootstrap the application with.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
@@ -92,7 +82,14 @@ const program = new Commander.Command(packageJson.name)
|
||||
"--files <path>",
|
||||
`
|
||||
|
||||
Specify the path to a local file or folder for chatting.
|
||||
Specify the path to a local file or folder for chatting.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--example-file",
|
||||
`
|
||||
|
||||
Select to use an example PDF as data source.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
@@ -114,19 +111,6 @@ const program = new Commander.Command(packageJson.name)
|
||||
`
|
||||
|
||||
Whether to generate a frontend for your backend.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--model <model>",
|
||||
`
|
||||
|
||||
Select OpenAI model to use. E.g. gpt-3.5-turbo.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--embedding-model <embeddingModel>",
|
||||
`
|
||||
Select OpenAI embedding model to use. E.g. text-embedding-ada-002.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
@@ -165,15 +149,31 @@ const program = new Commander.Command(packageJson.name)
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--llama-parse",
|
||||
"--use-llama-parse",
|
||||
`
|
||||
Enable LlamaParse.
|
||||
|
||||
Enable LlamaParse.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--llama-cloud-key <key>",
|
||||
`
|
||||
|
||||
Provide a LlamaCloud API key.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--observability <observability>",
|
||||
`
|
||||
|
||||
Specify observability tools to use. Eg: none, opentelemetry
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--ask-models",
|
||||
`
|
||||
|
||||
Select LLM and embedding models.
|
||||
`,
|
||||
)
|
||||
.allowUnknownOption()
|
||||
@@ -181,9 +181,6 @@ const program = new Commander.Command(packageJson.name)
|
||||
if (process.argv.includes("--no-frontend")) {
|
||||
program.frontend = false;
|
||||
}
|
||||
if (process.argv.includes("--no-eslint")) {
|
||||
program.eslint = false;
|
||||
}
|
||||
if (process.argv.includes("--tools")) {
|
||||
if (program.tools === "none") {
|
||||
program.tools = [];
|
||||
@@ -192,7 +189,13 @@ if (process.argv.includes("--tools")) {
|
||||
}
|
||||
}
|
||||
if (process.argv.includes("--no-llama-parse")) {
|
||||
program.llamaParse = false;
|
||||
program.useLlamaParse = false;
|
||||
}
|
||||
program.askModels = process.argv.includes("--ask-models");
|
||||
if (process.argv.includes("--no-files")) {
|
||||
program.dataSources = [];
|
||||
} else {
|
||||
program.dataSources = getDataSources(program.files, program.exampleFile);
|
||||
}
|
||||
|
||||
const packageManager = !!program.useNpm
|
||||
@@ -275,28 +278,29 @@ async function run(): Promise<void> {
|
||||
}
|
||||
|
||||
const preferences = (conf.get("preferences") || {}) as QuestionArgs;
|
||||
await askQuestions(program as unknown as QuestionArgs, preferences);
|
||||
await askQuestions(
|
||||
program as unknown as QuestionArgs,
|
||||
preferences,
|
||||
program.openAiKey,
|
||||
);
|
||||
|
||||
await createApp({
|
||||
template: program.template,
|
||||
framework: program.framework,
|
||||
engine: program.engine,
|
||||
ui: program.ui,
|
||||
appPath: resolvedProjectPath,
|
||||
packageManager,
|
||||
eslint: program.eslint,
|
||||
frontend: program.frontend,
|
||||
openAiKey: program.openAiKey,
|
||||
modelConfig: program.modelConfig,
|
||||
llamaCloudKey: program.llamaCloudKey,
|
||||
model: program.model,
|
||||
embeddingModel: program.embeddingModel,
|
||||
communityProjectConfig: program.communityProjectConfig,
|
||||
llamapack: program.llamapack,
|
||||
vectorDb: program.vectorDb,
|
||||
externalPort: program.externalPort,
|
||||
postInstallAction: program.postInstallAction,
|
||||
dataSource: program.dataSource,
|
||||
dataSources: program.dataSources,
|
||||
tools: program.tools,
|
||||
useLlamaParse: program.useLlamaParse,
|
||||
observability: program.observability,
|
||||
});
|
||||
conf.set("preferences", preferences);
|
||||
|
||||
+25
-12
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "create-llama",
|
||||
"version": "0.0.29",
|
||||
"version": "0.1.8",
|
||||
"description": "Create LlamaIndex-powered apps with one command",
|
||||
"keywords": [
|
||||
"rag",
|
||||
"llamaindex",
|
||||
"next.js"
|
||||
],
|
||||
"description": "Create LlamaIndex-powered apps with one command",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/run-llama/LlamaIndexTS",
|
||||
@@ -20,27 +20,30 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "bash ./scripts/build.sh",
|
||||
"build:ncc": "pnpm run clean && ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
|
||||
"clean": "rimraf --glob ./dist ./templates/**/__pycache__ ./templates/**/node_modules ./templates/**/poetry.lock",
|
||||
"dev": "ncc build ./index.ts -w -o dist/",
|
||||
"e2e": "playwright test",
|
||||
"format": "prettier --ignore-unknown --cache --check .",
|
||||
"format:write": "prettier --ignore-unknown --write .",
|
||||
"dev": "ncc build ./index.ts -w -o dist/",
|
||||
"build": "npm run clean && ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
|
||||
"lint": "eslint . --ignore-pattern dist --ignore-pattern e2e/cache",
|
||||
"e2e": "playwright test",
|
||||
"new-snapshot": "pnpm run build && changeset version --snapshot",
|
||||
"new-version": "pnpm run build && changeset version",
|
||||
"pack-install": "bash ./scripts/pack.sh",
|
||||
"prepare": "husky",
|
||||
"release": "pnpm run build && changeset publish",
|
||||
"new-version": "pnpm run build && changeset version"
|
||||
"release-snapshot": "pnpm run build && changeset publish --tag snapshot"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.41.1",
|
||||
"dependencies": {
|
||||
"@types/async-retry": "1.4.2",
|
||||
"@types/ci-info": "2.0.0",
|
||||
"@types/cross-spawn": "6.0.0",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/node": "^20.11.7",
|
||||
"@types/prompts": "2.0.1",
|
||||
"@types/tar": "6.1.5",
|
||||
"@types/validate-npm-package-name": "3.0.0",
|
||||
"@vercel/ncc": "0.38.1",
|
||||
"async-retry": "1.3.1",
|
||||
"async-sema": "3.0.1",
|
||||
"ci-info": "github:watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
|
||||
@@ -48,24 +51,34 @@
|
||||
"conf": "10.2.0",
|
||||
"cross-spawn": "7.0.3",
|
||||
"fast-glob": "3.3.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "10.7.0",
|
||||
"ollama": "^0.5.0",
|
||||
"ora": "^8.0.1",
|
||||
"picocolors": "1.0.0",
|
||||
"prompts": "2.1.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"smol-toml": "^1.1.4",
|
||||
"tar": "6.1.15",
|
||||
"terminal-link": "^3.0.0",
|
||||
"update-check": "1.5.4",
|
||||
"validate-npm-package-name": "3.0.0",
|
||||
"wait-port": "^1.1.0",
|
||||
"yaml": "2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"@playwright/test": "^1.41.1",
|
||||
"@vercel/ncc": "0.38.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"husky": "^9.0.10",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"rimraf": "^5.0.5",
|
||||
"typescript": "^5.3.3",
|
||||
"eslint-config-prettier": "^8.10.0"
|
||||
"wait-port": "^1.1.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.0.5",
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
}
|
||||
|
||||
Generated
+2725
-3348
File diff suppressed because it is too large
Load Diff
+243
-338
@@ -6,20 +6,24 @@ import { blue, green, red } from "picocolors";
|
||||
import prompts from "prompts";
|
||||
import { InstallAppArgs } from "./create-app";
|
||||
import {
|
||||
FileSourceConfig,
|
||||
TemplateDataSource,
|
||||
TemplateDataSourceType,
|
||||
TemplateFramework,
|
||||
} from "./helpers";
|
||||
import { COMMUNITY_OWNER, COMMUNITY_REPO } from "./helpers/constant";
|
||||
import { EXAMPLE_FILE } from "./helpers/datasources";
|
||||
import { templatesDir } from "./helpers/dir";
|
||||
import { getAvailableLlamapackOptions } from "./helpers/llama-pack";
|
||||
import { askModelConfig } from "./helpers/providers";
|
||||
import { getProjectOptions } from "./helpers/repo";
|
||||
import { supportedTools, toolsRequireConfig } from "./helpers/tools";
|
||||
|
||||
export type QuestionArgs = Omit<
|
||||
InstallAppArgs,
|
||||
"appPath" | "packageManager"
|
||||
> & { files?: string; llamaParse?: boolean };
|
||||
> & {
|
||||
askModels?: boolean;
|
||||
};
|
||||
const supportedContextFileTypes = [
|
||||
".pdf",
|
||||
".doc",
|
||||
@@ -32,21 +36,22 @@ const MACOS_FILE_SELECTION_SCRIPT = `
|
||||
osascript -l JavaScript -e '
|
||||
a = Application.currentApplication();
|
||||
a.includeStandardAdditions = true;
|
||||
a.chooseFile({ withPrompt: "Please select a file to process:" }).toString()
|
||||
a.chooseFile({ withPrompt: "Please select files to process:", multipleSelectionsAllowed: true }).map(file => file.toString())
|
||||
'`;
|
||||
const MACOS_FOLDER_SELECTION_SCRIPT = `
|
||||
osascript -l JavaScript -e '
|
||||
a = Application.currentApplication();
|
||||
a.includeStandardAdditions = true;
|
||||
a.chooseFolder({ withPrompt: "Please select a folder to process:" }).toString()
|
||||
a.chooseFolder({ withPrompt: "Please select folders to process:", multipleSelectionsAllowed: true }).map(folder => folder.toString())
|
||||
'`;
|
||||
const WINDOWS_FILE_SELECTION_SCRIPT = `
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$openFileDialog.InitialDirectory = [Environment]::GetFolderPath('Desktop')
|
||||
$openFileDialog.Multiselect = $true
|
||||
$result = $openFileDialog.ShowDialog()
|
||||
if ($result -eq 'OK') {
|
||||
$openFileDialog.FileName
|
||||
$openFileDialog.FileNames
|
||||
}
|
||||
`;
|
||||
const WINDOWS_FOLDER_SELECTION_SCRIPT = `
|
||||
@@ -59,28 +64,21 @@ if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK)
|
||||
}
|
||||
`;
|
||||
|
||||
const defaults: QuestionArgs = {
|
||||
const defaults: Omit<QuestionArgs, "modelConfig"> = {
|
||||
template: "streaming",
|
||||
framework: "nextjs",
|
||||
engine: "simple",
|
||||
ui: "html",
|
||||
eslint: true,
|
||||
ui: "shadcn",
|
||||
frontend: false,
|
||||
openAiKey: "",
|
||||
llamaCloudKey: "",
|
||||
model: "gpt-3.5-turbo",
|
||||
embeddingModel: "text-embedding-ada-002",
|
||||
useLlamaParse: false,
|
||||
communityProjectConfig: undefined,
|
||||
llamapack: "",
|
||||
postInstallAction: "dependencies",
|
||||
dataSource: {
|
||||
type: "none",
|
||||
config: {},
|
||||
},
|
||||
dataSources: [],
|
||||
tools: [],
|
||||
};
|
||||
|
||||
const handlers = {
|
||||
export const questionHandlers = {
|
||||
onCancel: () => {
|
||||
console.error("Exiting.");
|
||||
process.exit(1);
|
||||
@@ -96,6 +94,10 @@ const getVectorDbChoices = (framework: TemplateFramework) => {
|
||||
{ title: "MongoDB", value: "mongo" },
|
||||
{ title: "PostgreSQL", value: "pg" },
|
||||
{ title: "Pinecone", value: "pinecone" },
|
||||
{ title: "Milvus", value: "milvus" },
|
||||
{ title: "Astra", value: "astra" },
|
||||
{ title: "Qdrant", value: "qdrant" },
|
||||
{ title: "ChromaDB", value: "chroma" },
|
||||
];
|
||||
|
||||
const vectordbLang = framework === "fastapi" ? "python" : "typescript";
|
||||
@@ -113,29 +115,51 @@ const getVectorDbChoices = (framework: TemplateFramework) => {
|
||||
return displayedChoices;
|
||||
};
|
||||
|
||||
const getDataSourceChoices = (framework: TemplateFramework) => {
|
||||
const choices = [
|
||||
{
|
||||
title: "No data, just a simple chat",
|
||||
value: "simple",
|
||||
},
|
||||
{ title: "Use an example PDF", value: "exampleFile" },
|
||||
];
|
||||
if (process.platform === "win32" || process.platform === "darwin") {
|
||||
export const getDataSourceChoices = (
|
||||
framework: TemplateFramework,
|
||||
selectedDataSource: TemplateDataSource[],
|
||||
) => {
|
||||
const choices = [];
|
||||
if (selectedDataSource.length > 0) {
|
||||
choices.push({
|
||||
title: `Use a local file (${supportedContextFileTypes.join(", ")})`,
|
||||
value: "localFile",
|
||||
});
|
||||
choices.push({
|
||||
title: `Use a local folder`,
|
||||
value: "localFolder",
|
||||
title: "No",
|
||||
value: "no",
|
||||
});
|
||||
}
|
||||
if (selectedDataSource === undefined || selectedDataSource.length === 0) {
|
||||
choices.push({
|
||||
title: "No data, just a simple chat or agent",
|
||||
value: "none",
|
||||
});
|
||||
choices.push({
|
||||
title: "Use an example PDF",
|
||||
value: "exampleFile",
|
||||
});
|
||||
}
|
||||
|
||||
choices.push(
|
||||
{
|
||||
title: `Use local files (${supportedContextFileTypes.join(", ")})`,
|
||||
value: "file",
|
||||
},
|
||||
{
|
||||
title:
|
||||
process.platform === "win32"
|
||||
? "Use a local folder"
|
||||
: "Use local folders",
|
||||
value: "folder",
|
||||
},
|
||||
);
|
||||
|
||||
if (framework === "fastapi") {
|
||||
choices.push({
|
||||
title: "Use website content (requires Chrome)",
|
||||
value: "web",
|
||||
});
|
||||
choices.push({
|
||||
title: "Use data from a database (Mysql, PostgreSQL)",
|
||||
value: "db",
|
||||
});
|
||||
}
|
||||
return choices;
|
||||
};
|
||||
@@ -164,9 +188,16 @@ const selectLocalContextData = async (type: TemplateDataSourceType) => {
|
||||
process.exit(1);
|
||||
}
|
||||
selectedPath = execSync(execScript, execOpts).toString().trim();
|
||||
if (type === "file") {
|
||||
const fileType = path.extname(selectedPath);
|
||||
if (!supportedContextFileTypes.includes(fileType)) {
|
||||
const paths =
|
||||
process.platform === "win32"
|
||||
? selectedPath.split("\r\n")
|
||||
: selectedPath.split(", ");
|
||||
|
||||
for (const p of paths) {
|
||||
if (
|
||||
fs.statSync(p).isFile() &&
|
||||
!supportedContextFileTypes.includes(path.extname(p))
|
||||
) {
|
||||
console.log(
|
||||
red(
|
||||
`Please select a supported file type: ${supportedContextFileTypes}`,
|
||||
@@ -175,7 +206,7 @@ const selectLocalContextData = async (type: TemplateDataSourceType) => {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
return selectedPath;
|
||||
return paths;
|
||||
} catch (error) {
|
||||
console.log(
|
||||
red(
|
||||
@@ -199,10 +230,12 @@ export const onPromptState = (state: any) => {
|
||||
export const askQuestions = async (
|
||||
program: QuestionArgs,
|
||||
preferences: QuestionArgs,
|
||||
openAiKey?: string,
|
||||
) => {
|
||||
const getPrefOrDefault = <K extends keyof QuestionArgs>(
|
||||
const getPrefOrDefault = <K extends keyof Omit<QuestionArgs, "modelConfig">>(
|
||||
field: K,
|
||||
): QuestionArgs[K] => preferences[field] ?? defaults[field];
|
||||
): Omit<QuestionArgs, "modelConfig">[K] =>
|
||||
preferences[field] ?? defaults[field];
|
||||
|
||||
// Ask for next action after installation
|
||||
async function askPostInstallAction() {
|
||||
@@ -225,22 +258,19 @@ export const askQuestions = async (
|
||||
},
|
||||
];
|
||||
|
||||
const openAiKeyConfigured =
|
||||
program.openAiKey || process.env["OPENAI_API_KEY"];
|
||||
const modelConfigured =
|
||||
!program.llamapack && program.modelConfig.isConfigured();
|
||||
// If using LlamaParse, require LlamaCloud API key
|
||||
const llamaCloudKeyConfigured = (
|
||||
program.dataSource?.config as FileSourceConfig
|
||||
)?.useLlamaParse
|
||||
const llamaCloudKeyConfigured = program.useLlamaParse
|
||||
? program.llamaCloudKey || process.env["LLAMA_CLOUD_API_KEY"]
|
||||
: true;
|
||||
const hasVectorDb = program.vectorDb && program.vectorDb !== "none";
|
||||
// Can run the app if all tools do not require configuration
|
||||
if (
|
||||
!hasVectorDb &&
|
||||
openAiKeyConfigured &&
|
||||
modelConfigured &&
|
||||
llamaCloudKeyConfigured &&
|
||||
!toolsRequireConfig(program.tools) &&
|
||||
!program.llamapack
|
||||
!toolsRequireConfig(program.tools)
|
||||
) {
|
||||
actionChoices.push({
|
||||
title:
|
||||
@@ -257,7 +287,7 @@ export const askQuestions = async (
|
||||
choices: actionChoices,
|
||||
initial: 1,
|
||||
},
|
||||
handlers,
|
||||
questionHandlers,
|
||||
);
|
||||
|
||||
program.postInstallAction = action;
|
||||
@@ -278,8 +308,7 @@ export const askQuestions = async (
|
||||
name: "template",
|
||||
message: "Which template would you like to use?",
|
||||
choices: [
|
||||
{ title: "Chat without streaming", value: "simple" },
|
||||
{ title: "Chat with streaming", value: "streaming" },
|
||||
{ title: "Chat", value: "streaming" },
|
||||
{
|
||||
title: `Community template from ${styledRepo}`,
|
||||
value: "community",
|
||||
@@ -289,9 +318,9 @@ export const askQuestions = async (
|
||||
value: "llamapack",
|
||||
},
|
||||
],
|
||||
initial: 1,
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
questionHandlers,
|
||||
);
|
||||
program.template = template;
|
||||
preferences.template = template;
|
||||
@@ -314,7 +343,7 @@ export const askQuestions = async (
|
||||
})),
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
questionHandlers,
|
||||
);
|
||||
const projectConfig = JSON.parse(communityProjectConfig);
|
||||
program.communityProjectConfig = projectConfig;
|
||||
@@ -335,7 +364,7 @@ export const askQuestions = async (
|
||||
})),
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
questionHandlers,
|
||||
);
|
||||
program.llamapack = llamapack;
|
||||
preferences.llamapack = llamapack;
|
||||
@@ -348,13 +377,10 @@ export const askQuestions = async (
|
||||
program.framework = getPrefOrDefault("framework");
|
||||
} else {
|
||||
const choices = [
|
||||
{ title: "NextJS", value: "nextjs" },
|
||||
{ title: "Express", value: "express" },
|
||||
{ title: "FastAPI (Python)", value: "fastapi" },
|
||||
];
|
||||
if (program.template === "streaming") {
|
||||
// allow NextJS only for streaming template
|
||||
choices.unshift({ title: "NextJS", value: "nextjs" });
|
||||
}
|
||||
|
||||
const { framework } = await prompts(
|
||||
{
|
||||
@@ -364,19 +390,15 @@ export const askQuestions = async (
|
||||
choices,
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
questionHandlers,
|
||||
);
|
||||
program.framework = framework;
|
||||
preferences.framework = framework;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
program.template === "streaming" &&
|
||||
(program.framework === "express" || program.framework === "fastapi")
|
||||
) {
|
||||
if (program.framework === "express" || program.framework === "fastapi") {
|
||||
// if a backend-only framework is selected, ask whether we should create a frontend
|
||||
// (only for streaming backends)
|
||||
if (program.frontend === undefined) {
|
||||
if (ciInfo.isCI) {
|
||||
program.frontend = getPrefOrDefault("frontend");
|
||||
@@ -408,237 +430,187 @@ export const askQuestions = async (
|
||||
|
||||
if (program.framework === "nextjs" || program.frontend) {
|
||||
if (!program.ui) {
|
||||
if (ciInfo.isCI) {
|
||||
program.ui = getPrefOrDefault("ui");
|
||||
} else {
|
||||
const { ui } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "ui",
|
||||
message: "Which UI would you like to use?",
|
||||
choices: [
|
||||
{ title: "Just HTML", value: "html" },
|
||||
{ title: "Shadcn", value: "shadcn" },
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.ui = ui;
|
||||
preferences.ui = ui;
|
||||
}
|
||||
program.ui = defaults.ui;
|
||||
}
|
||||
}
|
||||
|
||||
if (program.framework === "express" || program.framework === "nextjs") {
|
||||
if (!program.observability) {
|
||||
if (ciInfo.isCI) {
|
||||
program.observability = getPrefOrDefault("observability");
|
||||
} else {
|
||||
const { observability } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "observability",
|
||||
message: "Would you like to set up observability?",
|
||||
choices: [
|
||||
{ title: "No", value: "none" },
|
||||
{ title: "OpenTelemetry", value: "opentelemetry" },
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
|
||||
program.observability = observability;
|
||||
preferences.observability = observability;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.model) {
|
||||
if (!program.observability) {
|
||||
if (ciInfo.isCI) {
|
||||
program.model = getPrefOrDefault("model");
|
||||
program.observability = getPrefOrDefault("observability");
|
||||
} else {
|
||||
const { model } = await prompts(
|
||||
const { observability } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "model",
|
||||
message: "Which model would you like to use?",
|
||||
name: "observability",
|
||||
message: "Would you like to set up observability?",
|
||||
choices: [
|
||||
{ title: "gpt-3.5-turbo", value: "gpt-3.5-turbo-0125" },
|
||||
{ title: "gpt-4-turbo-preview", value: "gpt-4-turbo-preview" },
|
||||
{ title: "gpt-4", value: "gpt-4" },
|
||||
{
|
||||
title: "gpt-4-vision-preview",
|
||||
value: "gpt-4-vision-preview",
|
||||
},
|
||||
{ title: "No", value: "none" },
|
||||
{ title: "OpenTelemetry", value: "opentelemetry" },
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
questionHandlers,
|
||||
);
|
||||
program.model = model;
|
||||
preferences.model = model;
|
||||
|
||||
program.observability = observability;
|
||||
preferences.observability = observability;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.embeddingModel && program.framework === "fastapi") {
|
||||
if (!program.modelConfig) {
|
||||
const modelConfig = await askModelConfig({
|
||||
openAiKey,
|
||||
askModels: program.askModels ?? false,
|
||||
});
|
||||
program.modelConfig = modelConfig;
|
||||
preferences.modelConfig = modelConfig;
|
||||
}
|
||||
|
||||
if (!program.dataSources) {
|
||||
if (ciInfo.isCI) {
|
||||
program.embeddingModel = getPrefOrDefault("embeddingModel");
|
||||
program.dataSources = getPrefOrDefault("dataSources");
|
||||
} else {
|
||||
const { embeddingModel } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "embeddingModel",
|
||||
message: "Which embedding model would you like to use?",
|
||||
choices: [
|
||||
{
|
||||
title: "text-embedding-ada-002",
|
||||
value: "text-embedding-ada-002",
|
||||
},
|
||||
{
|
||||
title: "text-embedding-3-small",
|
||||
value: "text-embedding-3-small",
|
||||
},
|
||||
{
|
||||
title: "text-embedding-3-large",
|
||||
value: "text-embedding-3-large",
|
||||
},
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.embeddingModel = embeddingModel;
|
||||
preferences.embeddingModel = embeddingModel;
|
||||
}
|
||||
}
|
||||
program.dataSources = [];
|
||||
// continue asking user for data sources if none are initially provided
|
||||
while (true) {
|
||||
const firstQuestion = program.dataSources.length === 0;
|
||||
const { selectedSource } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "selectedSource",
|
||||
message: firstQuestion
|
||||
? "Which data source would you like to use?"
|
||||
: "Would you like to add another data source?",
|
||||
choices: getDataSourceChoices(
|
||||
program.framework,
|
||||
program.dataSources,
|
||||
),
|
||||
initial: firstQuestion ? 1 : 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
|
||||
if (program.files) {
|
||||
// If user specified files option, then the program should use context engine
|
||||
program.engine == "context";
|
||||
if (!fs.existsSync(program.files)) {
|
||||
console.log("File or folder not found");
|
||||
process.exit(1);
|
||||
} else {
|
||||
program.dataSource = {
|
||||
type: fs.lstatSync(program.files).isDirectory() ? "folder" : "file",
|
||||
config: {
|
||||
path: program.files,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
if (selectedSource === "no" || selectedSource === "none") {
|
||||
// user doesn't want another data source or any data source
|
||||
break;
|
||||
}
|
||||
switch (selectedSource) {
|
||||
case "exampleFile": {
|
||||
program.dataSources.push(EXAMPLE_FILE);
|
||||
break;
|
||||
}
|
||||
case "file":
|
||||
case "folder": {
|
||||
const selectedPaths = await selectLocalContextData(selectedSource);
|
||||
for (const p of selectedPaths) {
|
||||
program.dataSources.push({
|
||||
type: "file",
|
||||
config: {
|
||||
path: p,
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "web": {
|
||||
const { baseUrl } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "baseUrl",
|
||||
message: "Please provide base URL of the website: ",
|
||||
initial: "https://www.llamaindex.ai",
|
||||
validate: (value: string) => {
|
||||
if (!value.includes("://")) {
|
||||
value = `https://${value}`;
|
||||
}
|
||||
const urlObj = new URL(value);
|
||||
if (
|
||||
urlObj.protocol !== "https:" &&
|
||||
urlObj.protocol !== "http:"
|
||||
) {
|
||||
return `URL=${value} has invalid protocol, only allow http or https`;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
|
||||
if (!program.engine) {
|
||||
if (ciInfo.isCI) {
|
||||
program.engine = getPrefOrDefault("engine");
|
||||
} else {
|
||||
const { dataSource } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "dataSource",
|
||||
message: "Which data source would you like to use?",
|
||||
choices: getDataSourceChoices(program.framework),
|
||||
initial: 1,
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
// Initialize with default config
|
||||
program.dataSource = getPrefOrDefault("dataSource");
|
||||
if (program.dataSource) {
|
||||
switch (dataSource) {
|
||||
case "simple":
|
||||
program.engine = "simple";
|
||||
program.dataSource = { type: "none", config: {} };
|
||||
break;
|
||||
case "exampleFile":
|
||||
program.engine = "context";
|
||||
// Treat example as a folder data source with no config
|
||||
program.dataSource = { type: "folder", config: {} };
|
||||
break;
|
||||
case "localFile":
|
||||
program.engine = "context";
|
||||
program.dataSource = {
|
||||
type: "file",
|
||||
program.dataSources.push({
|
||||
type: "web",
|
||||
config: {
|
||||
path: await selectLocalContextData("file"),
|
||||
baseUrl,
|
||||
prefix: baseUrl,
|
||||
depth: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
break;
|
||||
case "localFolder":
|
||||
program.engine = "context";
|
||||
program.dataSource = {
|
||||
type: "folder",
|
||||
config: {
|
||||
path: await selectLocalContextData("folder"),
|
||||
}
|
||||
case "db": {
|
||||
const dbPrompts: prompts.PromptObject<string>[] = [
|
||||
{
|
||||
type: "text",
|
||||
name: "uri",
|
||||
message:
|
||||
"Please enter the connection string (URI) for the database.",
|
||||
initial: "mysql+pymysql://user:pass@localhost:3306/mydb",
|
||||
validate: (value: string) => {
|
||||
if (!value) {
|
||||
return "Please provide a valid connection string";
|
||||
} else if (
|
||||
!(
|
||||
value.startsWith("mysql+pymysql://") ||
|
||||
value.startsWith("postgresql+psycopg://")
|
||||
)
|
||||
) {
|
||||
return "The connection string must start with 'mysql+pymysql://' for MySQL or 'postgresql+psycopg://' for PostgreSQL";
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "web":
|
||||
program.engine = "context";
|
||||
program.dataSource.type = "web";
|
||||
break;
|
||||
// Only ask for a query, user can provide more complex queries in the config file later
|
||||
{
|
||||
type: (prev) => (prev ? "text" : null),
|
||||
name: "queries",
|
||||
message: "Please enter the SQL query to fetch data:",
|
||||
initial: "SELECT * FROM mytable",
|
||||
},
|
||||
];
|
||||
program.dataSources.push({
|
||||
type: "db",
|
||||
config: await prompts(dbPrompts, questionHandlers),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!program.dataSource) {
|
||||
// Handle a case when engine is specified but dataSource is not
|
||||
if (program.engine === "context") {
|
||||
program.dataSource = {
|
||||
type: "folder",
|
||||
config: {},
|
||||
};
|
||||
} else if (program.engine === "simple") {
|
||||
program.dataSource = {
|
||||
type: "none",
|
||||
config: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Asking for LlamaParse if user selected file or folder data source
|
||||
if (
|
||||
(program.dataSource?.type === "file" ||
|
||||
program.dataSource?.type === "folder") &&
|
||||
program.framework === "fastapi"
|
||||
program.dataSources.some((ds) => ds.type === "file") &&
|
||||
program.useLlamaParse === undefined
|
||||
) {
|
||||
if (ciInfo.isCI) {
|
||||
program.useLlamaParse = getPrefOrDefault("useLlamaParse");
|
||||
program.llamaCloudKey = getPrefOrDefault("llamaCloudKey");
|
||||
} else {
|
||||
const dataSourceConfig = program.dataSource.config as FileSourceConfig;
|
||||
dataSourceConfig.useLlamaParse = program.llamaParse;
|
||||
|
||||
// Is pdf file selected as data source or is it a folder data source
|
||||
const askingLlamaParse =
|
||||
dataSourceConfig.useLlamaParse === undefined &&
|
||||
(program.dataSource.type === "folder"
|
||||
? true
|
||||
: dataSourceConfig.path &&
|
||||
path.extname(dataSourceConfig.path) === ".pdf");
|
||||
|
||||
// Ask if user wants to use LlamaParse
|
||||
if (askingLlamaParse) {
|
||||
const { useLlamaParse } = await prompts(
|
||||
{
|
||||
type: "toggle",
|
||||
name: "useLlamaParse",
|
||||
message:
|
||||
"Would you like to use LlamaParse (improved parser for RAG - requires API key)?",
|
||||
initial: true,
|
||||
active: "yes",
|
||||
inactive: "no",
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
dataSourceConfig.useLlamaParse = useLlamaParse;
|
||||
program.dataSource.config = dataSourceConfig;
|
||||
}
|
||||
const { useLlamaParse } = await prompts(
|
||||
{
|
||||
type: "toggle",
|
||||
name: "useLlamaParse",
|
||||
message:
|
||||
"Would you like to use LlamaParse (improved parser for RAG - requires API key)?",
|
||||
initial: false,
|
||||
active: "yes",
|
||||
inactive: "no",
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
program.useLlamaParse = useLlamaParse;
|
||||
|
||||
// Ask for LlamaCloud API key
|
||||
if (
|
||||
dataSourceConfig.useLlamaParse &&
|
||||
program.llamaCloudKey === undefined
|
||||
) {
|
||||
if (useLlamaParse && program.llamaCloudKey === undefined) {
|
||||
const { llamaCloudKey } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
@@ -646,46 +618,14 @@ export const askQuestions = async (
|
||||
message:
|
||||
"Please provide your LlamaIndex Cloud API key (leave blank to skip):",
|
||||
},
|
||||
handlers,
|
||||
questionHandlers,
|
||||
);
|
||||
program.llamaCloudKey = llamaCloudKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (program.dataSource?.type === "web" && program.framework === "fastapi") {
|
||||
let { baseUrl } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "baseUrl",
|
||||
message: "Please provide base URL of the website:",
|
||||
initial: "https://www.llamaindex.ai",
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
try {
|
||||
if (!baseUrl.includes("://")) {
|
||||
baseUrl = `https://${baseUrl}`;
|
||||
}
|
||||
const checkUrl = new URL(baseUrl);
|
||||
if (checkUrl.protocol !== "https:" && checkUrl.protocol !== "http:") {
|
||||
throw new Error("Invalid protocol");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
red(
|
||||
"Invalid URL provided! Please provide a valid URL (e.g. https://www.llamaindex.ai)",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
program.dataSource.config = {
|
||||
baseUrl: baseUrl,
|
||||
depth: 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (program.engine !== "simple" && !program.vectorDb) {
|
||||
if (program.dataSources.length > 0 && !program.vectorDb) {
|
||||
if (ciInfo.isCI) {
|
||||
program.vectorDb = getPrefOrDefault("vectorDb");
|
||||
} else {
|
||||
@@ -697,22 +637,21 @@ export const askQuestions = async (
|
||||
choices: getVectorDbChoices(program.framework),
|
||||
initial: 0,
|
||||
},
|
||||
handlers,
|
||||
questionHandlers,
|
||||
);
|
||||
program.vectorDb = vectorDb;
|
||||
preferences.vectorDb = vectorDb;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!program.tools &&
|
||||
program.framework === "fastapi" &&
|
||||
program.engine === "context"
|
||||
) {
|
||||
if (!program.tools) {
|
||||
if (ciInfo.isCI) {
|
||||
program.tools = getPrefOrDefault("tools");
|
||||
} else {
|
||||
const toolChoices = supportedTools.map((tool) => ({
|
||||
const options = supportedTools.filter((t) =>
|
||||
t.supportedFrameworks?.includes(program.framework),
|
||||
);
|
||||
const toolChoices = options.map((tool) => ({
|
||||
title: tool.display,
|
||||
value: tool.name,
|
||||
}));
|
||||
@@ -731,43 +670,9 @@ export const askQuestions = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.openAiKey) {
|
||||
const { key } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "key",
|
||||
message: "Please provide your OpenAI API key (leave blank to skip):",
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
program.openAiKey = key;
|
||||
preferences.openAiKey = key;
|
||||
}
|
||||
|
||||
if (program.framework !== "fastapi" && program.eslint === undefined) {
|
||||
if (ciInfo.isCI) {
|
||||
program.eslint = getPrefOrDefault("eslint");
|
||||
} else {
|
||||
const styledEslint = blue("ESLint");
|
||||
const { eslint } = await prompts({
|
||||
onState: onPromptState,
|
||||
type: "toggle",
|
||||
name: "eslint",
|
||||
message: `Would you like to use ${styledEslint}?`,
|
||||
initial: getPrefOrDefault("eslint"),
|
||||
active: "Yes",
|
||||
inactive: "No",
|
||||
});
|
||||
program.eslint = Boolean(eslint);
|
||||
preferences.eslint = Boolean(eslint);
|
||||
}
|
||||
}
|
||||
|
||||
await askPostInstallAction();
|
||||
|
||||
// TODO: consider using zod to validate the input (doesn't work like this as not every option is required)
|
||||
// templateUISchema.parse(program.ui);
|
||||
// templateEngineSchema.parse(program.engine);
|
||||
// templateFrameworkSchema.parse(program.framework);
|
||||
// templateTypeSchema.parse(program.template);``
|
||||
};
|
||||
|
||||
export const toChoice = (value: string) => {
|
||||
return { title: value, value };
|
||||
};
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# build dist/index.js file
|
||||
pnpm run build:ncc
|
||||
|
||||
# add shebang to the top of dist/index.js
|
||||
# XXX: Windows needs a space after `node` to work correctly
|
||||
# Note: ncc can handle shebang but it didn't work with Windows in our tests
|
||||
echo '#!/usr/bin/env node ' | cat - dist/index.js >temp && mv temp dist/index.js
|
||||
|
||||
# make dist/index.js executable
|
||||
chmod +x dist/index.js
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
pnpm pack && npm install -g $(pwd)/$(ls ./*.tgz | head -1)
|
||||
@@ -0,0 +1,26 @@
|
||||
FROM python:3.11 as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV PYTHONPATH=/app
|
||||
|
||||
# Install Poetry
|
||||
RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/opt/poetry python && \
|
||||
cd /usr/local/bin && \
|
||||
ln -s /opt/poetry/bin/poetry && \
|
||||
poetry config virtualenvs.create false
|
||||
|
||||
# Install Chromium for web loader
|
||||
# Can disable this if you don't use the web loader to reduce the image size
|
||||
RUN apt update && apt install -y chromium chromium-driver
|
||||
|
||||
# Install dependencies
|
||||
COPY ./pyproject.toml ./poetry.lock* /app/
|
||||
RUN poetry install --no-root --no-cache --only main
|
||||
|
||||
# ====================================
|
||||
FROM build as release
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM node:20-alpine as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package.json package-lock.* ./
|
||||
RUN npm install
|
||||
|
||||
# Build the application
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# ====================================
|
||||
FROM build as release
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -11,11 +11,12 @@ def get_chat_engine():
|
||||
top_k = os.getenv("TOP_K", "3")
|
||||
tools = []
|
||||
|
||||
# Add query tool
|
||||
# Add query tool if index exists
|
||||
index = get_index()
|
||||
query_engine = index.as_query_engine(similarity_top_k=int(top_k))
|
||||
query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine)
|
||||
tools.append(query_engine_tool)
|
||||
if index is not None:
|
||||
query_engine = index.as_query_engine(similarity_top_k=int(top_k))
|
||||
query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine)
|
||||
tools.append(query_engine_tool)
|
||||
|
||||
# Add additional tools
|
||||
tools += ToolFactory.from_env()
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import json
|
||||
import importlib
|
||||
|
||||
from llama_index.core.tools.tool_spec.base import BaseToolSpec
|
||||
from llama_index.core.tools.function_tool import FunctionTool
|
||||
|
||||
|
||||
class ToolFactory:
|
||||
|
||||
@staticmethod
|
||||
def create_tool(tool_name: str, **kwargs) -> list[FunctionTool]:
|
||||
try:
|
||||
tool_package, tool_cls_name = tool_name.split(".")
|
||||
module_name = f"llama_index.tools.{tool_package}"
|
||||
module = importlib.import_module(module_name)
|
||||
tool_class = getattr(module, tool_cls_name)
|
||||
tool_spec: BaseToolSpec = tool_class(**kwargs)
|
||||
return tool_spec.to_tool_list()
|
||||
except (ImportError, AttributeError) as e:
|
||||
raise ValueError(f"Unsupported tool: {tool_name}") from e
|
||||
except TypeError as e:
|
||||
raise ValueError(
|
||||
f"Could not create tool: {tool_name}. With config: {kwargs}"
|
||||
) from e
|
||||
|
||||
@staticmethod
|
||||
def from_env() -> list[FunctionTool]:
|
||||
tools = []
|
||||
with open("tools_config.json", "r") as f:
|
||||
tool_configs = json.load(f)
|
||||
for name, config in tool_configs.items():
|
||||
tools += ToolFactory.create_tool(name, **config)
|
||||
return tools
|
||||
@@ -0,0 +1,56 @@
|
||||
import os
|
||||
import yaml
|
||||
import importlib
|
||||
|
||||
from llama_index.core.tools.tool_spec.base import BaseToolSpec
|
||||
from llama_index.core.tools.function_tool import FunctionTool
|
||||
|
||||
|
||||
class ToolType:
|
||||
LLAMAHUB = "llamahub"
|
||||
LOCAL = "local"
|
||||
|
||||
|
||||
class ToolFactory:
|
||||
|
||||
TOOL_SOURCE_PACKAGE_MAP = {
|
||||
ToolType.LLAMAHUB: "llama_index.tools",
|
||||
ToolType.LOCAL: "app.engine.tools",
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def load_tools(tool_type: str, tool_name: str, config: dict) -> list[FunctionTool]:
|
||||
source_package = ToolFactory.TOOL_SOURCE_PACKAGE_MAP[tool_type]
|
||||
try:
|
||||
if "ToolSpec" in tool_name:
|
||||
tool_package, tool_cls_name = tool_name.split(".")
|
||||
module_name = f"{source_package}.{tool_package}"
|
||||
module = importlib.import_module(module_name)
|
||||
tool_class = getattr(module, tool_cls_name)
|
||||
tool_spec: BaseToolSpec = tool_class(**config)
|
||||
return tool_spec.to_tool_list()
|
||||
else:
|
||||
module = importlib.import_module(f"{source_package}.{tool_name}")
|
||||
tools = getattr(module, "tools")
|
||||
if not all(isinstance(tool, FunctionTool) for tool in tools):
|
||||
raise ValueError(
|
||||
f"The module {module} does not contain valid tools"
|
||||
)
|
||||
return tools
|
||||
except ImportError as e:
|
||||
raise ValueError(f"Failed to import tool {tool_name}: {e}")
|
||||
except AttributeError as e:
|
||||
raise ValueError(f"Failed to load tool {tool_name}: {e}")
|
||||
|
||||
@staticmethod
|
||||
def from_env() -> list[FunctionTool]:
|
||||
tools = []
|
||||
if os.path.exists("config/tools.yaml"):
|
||||
with open("config/tools.yaml", "r") as f:
|
||||
tool_configs = yaml.safe_load(f)
|
||||
for tool_type, config_entries in tool_configs.items():
|
||||
for tool_name, config in config_entries.items():
|
||||
tools.extend(
|
||||
ToolFactory.load_tools(tool_type, tool_name, config)
|
||||
)
|
||||
return tools
|
||||
@@ -0,0 +1,134 @@
|
||||
import os
|
||||
import logging
|
||||
import base64
|
||||
import uuid
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Tuple, Dict
|
||||
from llama_index.core.tools import FunctionTool
|
||||
from e2b_code_interpreter import CodeInterpreter
|
||||
from e2b_code_interpreter.models import Logs
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InterpreterExtraResult(BaseModel):
|
||||
type: str
|
||||
filename: str
|
||||
url: str
|
||||
|
||||
|
||||
class E2BToolOutput(BaseModel):
|
||||
is_error: bool
|
||||
logs: Logs
|
||||
results: List[InterpreterExtraResult] = []
|
||||
|
||||
|
||||
class E2BCodeInterpreter:
|
||||
|
||||
output_dir = "tool-output"
|
||||
|
||||
def __init__(self, api_key: str, filesever_url_prefix: str):
|
||||
self.api_key = api_key
|
||||
self.filesever_url_prefix = filesever_url_prefix
|
||||
|
||||
def get_output_path(self, filename: str) -> str:
|
||||
# if output directory doesn't exist, create it
|
||||
if not os.path.exists(self.output_dir):
|
||||
os.makedirs(self.output_dir, exist_ok=True)
|
||||
return os.path.join(self.output_dir, filename)
|
||||
|
||||
def save_to_disk(self, base64_data: str, ext: str) -> Dict:
|
||||
filename = f"{uuid.uuid4()}.{ext}" # generate a unique filename
|
||||
buffer = base64.b64decode(base64_data)
|
||||
output_path = self.get_output_path(filename)
|
||||
|
||||
try:
|
||||
with open(output_path, "wb") as file:
|
||||
file.write(buffer)
|
||||
except IOError as e:
|
||||
logger.error(f"Failed to write to file {output_path}: {str(e)}")
|
||||
raise e
|
||||
|
||||
logger.info(f"Saved file to {output_path}")
|
||||
|
||||
return {
|
||||
"outputPath": output_path,
|
||||
"filename": filename,
|
||||
}
|
||||
|
||||
def get_file_url(self, filename: str) -> str:
|
||||
return f"{self.filesever_url_prefix}/{self.output_dir}/{filename}"
|
||||
|
||||
def parse_result(self, result) -> List[InterpreterExtraResult]:
|
||||
"""
|
||||
The result could include multiple formats (e.g. png, svg, etc.) but encoded in base64
|
||||
We save each result to disk and return saved file metadata (extension, filename, url)
|
||||
"""
|
||||
if not result:
|
||||
return []
|
||||
|
||||
output = []
|
||||
|
||||
try:
|
||||
formats = result.formats()
|
||||
base64_data_arr = [result[format] for format in formats]
|
||||
|
||||
for ext, base64_data in zip(formats, base64_data_arr):
|
||||
if ext and base64_data:
|
||||
result = self.save_to_disk(base64_data, ext)
|
||||
filename = result["filename"]
|
||||
output.append(
|
||||
InterpreterExtraResult(
|
||||
type=ext, filename=filename, url=self.get_file_url(filename)
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error("Error when saving data to disk", error)
|
||||
|
||||
return output
|
||||
|
||||
def interpret(self, code: str) -> E2BToolOutput:
|
||||
with CodeInterpreter(api_key=self.api_key) as interpreter:
|
||||
logger.info(
|
||||
f"\n{'='*50}\n> Running following AI-generated code:\n{code}\n{'='*50}"
|
||||
)
|
||||
exec = interpreter.notebook.exec_cell(code)
|
||||
|
||||
if exec.error:
|
||||
output = E2BToolOutput(is_error=True, logs=[exec.error])
|
||||
else:
|
||||
if len(exec.results) == 0:
|
||||
output = E2BToolOutput(is_error=False, logs=exec.logs, results=[])
|
||||
else:
|
||||
results = self.parse_result(exec.results[0])
|
||||
output = E2BToolOutput(
|
||||
is_error=False, logs=exec.logs, results=results
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
def code_interpret(code: str) -> Dict:
|
||||
"""
|
||||
Execute python code in a Jupyter notebook cell and return any result, stdout, stderr, display_data, and error.
|
||||
"""
|
||||
api_key = os.getenv("E2B_API_KEY")
|
||||
filesever_url_prefix = os.getenv("FILESERVER_URL_PREFIX")
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
"E2B_API_KEY key is required to run code interpreter. Get it here: https://e2b.dev/docs/getting-started/api-key"
|
||||
)
|
||||
if not filesever_url_prefix:
|
||||
raise ValueError(
|
||||
"FILESERVER_URL_PREFIX is required to display file output from sandbox"
|
||||
)
|
||||
|
||||
interpreter = E2BCodeInterpreter(
|
||||
api_key=api_key, filesever_url_prefix=filesever_url_prefix
|
||||
)
|
||||
output = interpreter.interpret(code)
|
||||
return output.dict()
|
||||
|
||||
|
||||
# Specify as functions tools to be loaded by the ToolFactory
|
||||
tools = [FunctionTool.from_defaults(code_interpret)]
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Open Meteo weather map tool spec."""
|
||||
|
||||
import logging
|
||||
import requests
|
||||
import pytz
|
||||
from llama_index.core.tools import FunctionTool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OpenMeteoWeather:
|
||||
geo_api = "https://geocoding-api.open-meteo.com/v1"
|
||||
weather_api = "https://api.open-meteo.com/v1"
|
||||
|
||||
@classmethod
|
||||
def _get_geo_location(cls, location: str) -> dict:
|
||||
"""Get geo location from location name."""
|
||||
params = {"name": location, "count": 10, "language": "en", "format": "json"}
|
||||
response = requests.get(f"{cls.geo_api}/search", params=params)
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to fetch geo location: {response.status_code}")
|
||||
else:
|
||||
data = response.json()
|
||||
result = data["results"][0]
|
||||
geo_location = {
|
||||
"id": result["id"],
|
||||
"name": result["name"],
|
||||
"latitude": result["latitude"],
|
||||
"longitude": result["longitude"],
|
||||
}
|
||||
return geo_location
|
||||
|
||||
@classmethod
|
||||
def get_weather_information(cls, location: str) -> dict:
|
||||
"""Use this function to get the weather of any given location.
|
||||
Note that the weather code should follow WMO Weather interpretation codes (WW):
|
||||
0: Clear sky
|
||||
1, 2, 3: Mainly clear, partly cloudy, and overcast
|
||||
45, 48: Fog and depositing rime fog
|
||||
51, 53, 55: Drizzle: Light, moderate, and dense intensity
|
||||
56, 57: Freezing Drizzle: Light and dense intensity
|
||||
61, 63, 65: Rain: Slight, moderate and heavy intensity
|
||||
66, 67: Freezing Rain: Light and heavy intensity
|
||||
71, 73, 75: Snow fall: Slight, moderate, and heavy intensity
|
||||
77: Snow grains
|
||||
80, 81, 82: Rain showers: Slight, moderate, and violent
|
||||
85, 86: Snow showers slight and heavy
|
||||
95: Thunderstorm: Slight or moderate
|
||||
96, 99: Thunderstorm with slight and heavy hail
|
||||
"""
|
||||
logger.info(
|
||||
f"Calling open-meteo api to get weather information of location: {location}"
|
||||
)
|
||||
geo_location = cls._get_geo_location(location)
|
||||
timezone = pytz.timezone("UTC").zone
|
||||
params = {
|
||||
"latitude": geo_location["latitude"],
|
||||
"longitude": geo_location["longitude"],
|
||||
"current": "temperature_2m,weather_code",
|
||||
"hourly": "temperature_2m,weather_code",
|
||||
"daily": "weather_code",
|
||||
"timezone": timezone,
|
||||
}
|
||||
response = requests.get(f"{cls.weather_api}/forecast", params=params)
|
||||
if response.status_code != 200:
|
||||
raise Exception(
|
||||
f"Failed to fetch weather information: {response.status_code}"
|
||||
)
|
||||
return response.json()
|
||||
|
||||
|
||||
tools = [FunctionTool.from_defaults(OpenMeteoWeather.get_weather_information)]
|
||||
@@ -1,12 +1,22 @@
|
||||
import os
|
||||
from app.engine.index import get_index
|
||||
from fastapi import HTTPException
|
||||
|
||||
|
||||
def get_chat_engine():
|
||||
system_prompt = os.getenv("SYSTEM_PROMPT")
|
||||
top_k = os.getenv("TOP_K", 3)
|
||||
|
||||
return get_index().as_chat_engine(
|
||||
index = get_index()
|
||||
if index is None:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=str(
|
||||
"StorageContext is empty - call 'poetry run generate' to generate the storage first"
|
||||
),
|
||||
)
|
||||
|
||||
return index.as_chat_engine(
|
||||
similarity_top_k=int(top_k),
|
||||
system_prompt=system_prompt,
|
||||
chat_mode="condense_plus_context",
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { BaseToolWithCall, OpenAIAgent, QueryEngineTool } from "llamaindex";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { getDataSource } from "./index";
|
||||
import { STORAGE_CACHE_DIR } from "./shared";
|
||||
import { createTools } from "./tools";
|
||||
|
||||
export async function createChatEngine() {
|
||||
const tools: BaseToolWithCall[] = [];
|
||||
|
||||
// Add a query engine tool if we have a data source
|
||||
// Delete this code if you don't have a data source
|
||||
const index = await getDataSource();
|
||||
if (index) {
|
||||
tools.push(
|
||||
new QueryEngineTool({
|
||||
queryEngine: index.asQueryEngine(),
|
||||
metadata: {
|
||||
name: "data_query_engine",
|
||||
description: `A query engine for documents in storage folder: ${STORAGE_CACHE_DIR}`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const configFile = path.join("config", "tools.json");
|
||||
let toolConfig: any;
|
||||
try {
|
||||
// add tools from config file if it exists
|
||||
toolConfig = JSON.parse(await fs.readFile(configFile, "utf8"));
|
||||
} catch (e) {
|
||||
console.info(`Could not read ${configFile} file. Using no tools.`);
|
||||
}
|
||||
if (toolConfig) {
|
||||
tools.push(...(await createTools(toolConfig)));
|
||||
}
|
||||
|
||||
return new OpenAIAgent({
|
||||
tools,
|
||||
systemPrompt: process.env.SYSTEM_PROMPT,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { BaseToolWithCall } from "llamaindex";
|
||||
import { ToolsFactory } from "llamaindex/tools/ToolsFactory";
|
||||
import { InterpreterTool, InterpreterToolParams } from "./interpreter";
|
||||
import { WeatherTool, WeatherToolParams } from "./weather";
|
||||
|
||||
type ToolCreator = (config: unknown) => BaseToolWithCall;
|
||||
|
||||
export async function createTools(toolConfig: {
|
||||
local: Record<string, unknown>;
|
||||
llamahub: any;
|
||||
}): Promise<BaseToolWithCall[]> {
|
||||
// add local tools from the 'tools' folder (if configured)
|
||||
const tools = createLocalTools(toolConfig.local);
|
||||
// add tools from LlamaIndexTS (if configured)
|
||||
tools.push(...(await ToolsFactory.createTools(toolConfig.llamahub)));
|
||||
return tools;
|
||||
}
|
||||
|
||||
const toolFactory: Record<string, ToolCreator> = {
|
||||
weather: (config: unknown) => {
|
||||
return new WeatherTool(config as WeatherToolParams);
|
||||
},
|
||||
interpreter: (config: unknown) => {
|
||||
return new InterpreterTool(config as InterpreterToolParams);
|
||||
},
|
||||
};
|
||||
|
||||
function createLocalTools(
|
||||
localConfig: Record<string, unknown>,
|
||||
): BaseToolWithCall[] {
|
||||
const tools: BaseToolWithCall[] = [];
|
||||
|
||||
Object.keys(localConfig).forEach((key) => {
|
||||
if (key in toolFactory) {
|
||||
const toolConfig = localConfig[key];
|
||||
const tool = toolFactory[key](toolConfig);
|
||||
tools.push(tool);
|
||||
}
|
||||
});
|
||||
|
||||
return tools;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import { CodeInterpreter, Logs, Result } from "@e2b/code-interpreter";
|
||||
import type { JSONSchemaType } from "ajv";
|
||||
import fs from "fs";
|
||||
import { BaseTool, ToolMetadata } from "llamaindex";
|
||||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
|
||||
export type InterpreterParameter = {
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type InterpreterToolParams = {
|
||||
metadata?: ToolMetadata<JSONSchemaType<InterpreterParameter>>;
|
||||
apiKey?: string;
|
||||
fileServerURLPrefix?: string;
|
||||
};
|
||||
|
||||
export type InterpreterToolOuput = {
|
||||
isError: boolean;
|
||||
logs: Logs;
|
||||
extraResult: InterpreterExtraResult[];
|
||||
};
|
||||
|
||||
type InterpreterExtraType =
|
||||
| "html"
|
||||
| "markdown"
|
||||
| "svg"
|
||||
| "png"
|
||||
| "jpeg"
|
||||
| "pdf"
|
||||
| "latex"
|
||||
| "json"
|
||||
| "javascript";
|
||||
|
||||
export type InterpreterExtraResult = {
|
||||
type: InterpreterExtraType;
|
||||
filename: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
const DEFAULT_META_DATA: ToolMetadata<JSONSchemaType<InterpreterParameter>> = {
|
||||
name: "interpreter",
|
||||
description:
|
||||
"Execute python code in a Jupyter notebook cell and return any result, stdout, stderr, display_data, and error.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
code: {
|
||||
type: "string",
|
||||
description: "The python code to execute in a single cell.",
|
||||
},
|
||||
},
|
||||
required: ["code"],
|
||||
},
|
||||
};
|
||||
|
||||
export class InterpreterTool implements BaseTool<InterpreterParameter> {
|
||||
private readonly outputDir = "tool-output";
|
||||
private apiKey?: string;
|
||||
private fileServerURLPrefix?: string;
|
||||
metadata: ToolMetadata<JSONSchemaType<InterpreterParameter>>;
|
||||
codeInterpreter?: CodeInterpreter;
|
||||
|
||||
constructor(params?: InterpreterToolParams) {
|
||||
this.metadata = params?.metadata || DEFAULT_META_DATA;
|
||||
this.apiKey = params?.apiKey || process.env.E2B_API_KEY;
|
||||
this.fileServerURLPrefix =
|
||||
params?.fileServerURLPrefix || process.env.FILESERVER_URL_PREFIX;
|
||||
|
||||
if (!this.apiKey) {
|
||||
throw new Error(
|
||||
"E2B_API_KEY key is required to run code interpreter. Get it here: https://e2b.dev/docs/getting-started/api-key",
|
||||
);
|
||||
}
|
||||
if (!this.fileServerURLPrefix) {
|
||||
throw new Error(
|
||||
"FILESERVER_URL_PREFIX is required to display file output from sandbox",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async initInterpreter() {
|
||||
if (!this.codeInterpreter) {
|
||||
this.codeInterpreter = await CodeInterpreter.create({
|
||||
apiKey: this.apiKey,
|
||||
});
|
||||
}
|
||||
return this.codeInterpreter;
|
||||
}
|
||||
|
||||
public async codeInterpret(code: string): Promise<InterpreterToolOuput> {
|
||||
console.log(
|
||||
`\n${"=".repeat(50)}\n> Running following AI-generated code:\n${code}\n${"=".repeat(50)}`,
|
||||
);
|
||||
const interpreter = await this.initInterpreter();
|
||||
const exec = await interpreter.notebook.execCell(code);
|
||||
if (exec.error) console.error("[Code Interpreter error]", exec.error);
|
||||
const extraResult = await this.getExtraResult(exec.results[0]);
|
||||
const result: InterpreterToolOuput = {
|
||||
isError: !!exec.error,
|
||||
logs: exec.logs,
|
||||
extraResult,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
async call(input: InterpreterParameter): Promise<InterpreterToolOuput> {
|
||||
const result = await this.codeInterpret(input.code);
|
||||
await this.codeInterpreter?.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getExtraResult(
|
||||
res?: Result,
|
||||
): Promise<InterpreterExtraResult[]> {
|
||||
if (!res) return [];
|
||||
const output: InterpreterExtraResult[] = [];
|
||||
|
||||
try {
|
||||
const formats = res.formats(); // formats available for the result. Eg: ['png', ...]
|
||||
const base64DataArr = formats.map((f) => res[f as keyof Result]); // get base64 data for each format
|
||||
|
||||
// save base64 data to file and return the url
|
||||
for (let i = 0; i < formats.length; i++) {
|
||||
const ext = formats[i];
|
||||
const base64Data = base64DataArr[i];
|
||||
if (ext && base64Data) {
|
||||
const { filename } = this.saveToDisk(base64Data, ext);
|
||||
output.push({
|
||||
type: ext as InterpreterExtraType,
|
||||
filename,
|
||||
url: this.getFileUrl(filename),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error when saving data to disk", error);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Consider saving to cloud storage instead but it may cost more for you
|
||||
// See: https://e2b.dev/docs/sandbox/api/filesystem#write-to-file
|
||||
private saveToDisk(
|
||||
base64Data: string,
|
||||
ext: string,
|
||||
): {
|
||||
outputPath: string;
|
||||
filename: string;
|
||||
} {
|
||||
const filename = `${crypto.randomUUID()}.${ext}`; // generate a unique filename
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
const outputPath = this.getOutputPath(filename);
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
console.log(`Saved file to ${outputPath}`);
|
||||
return {
|
||||
outputPath,
|
||||
filename,
|
||||
};
|
||||
}
|
||||
|
||||
private getOutputPath(filename: string): string {
|
||||
// if outputDir doesn't exist, create it
|
||||
if (!fs.existsSync(this.outputDir)) {
|
||||
fs.mkdirSync(this.outputDir, { recursive: true });
|
||||
}
|
||||
return path.join(this.outputDir, filename);
|
||||
}
|
||||
|
||||
private getFileUrl(filename: string): string {
|
||||
return `${this.fileServerURLPrefix}/${this.outputDir}/${filename}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { JSONSchemaType } from "ajv";
|
||||
import { BaseTool, ToolMetadata } from "llamaindex";
|
||||
|
||||
interface GeoLocation {
|
||||
id: string;
|
||||
name: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
|
||||
export type WeatherParameter = {
|
||||
location: string;
|
||||
};
|
||||
|
||||
export type WeatherToolParams = {
|
||||
metadata?: ToolMetadata<JSONSchemaType<WeatherParameter>>;
|
||||
};
|
||||
|
||||
const DEFAULT_META_DATA: ToolMetadata<JSONSchemaType<WeatherParameter>> = {
|
||||
name: "get_weather_information",
|
||||
description: `
|
||||
Use this function to get the weather of any given location.
|
||||
Note that the weather code should follow WMO Weather interpretation codes (WW):
|
||||
0: Clear sky
|
||||
1, 2, 3: Mainly clear, partly cloudy, and overcast
|
||||
45, 48: Fog and depositing rime fog
|
||||
51, 53, 55: Drizzle: Light, moderate, and dense intensity
|
||||
56, 57: Freezing Drizzle: Light and dense intensity
|
||||
61, 63, 65: Rain: Slight, moderate and heavy intensity
|
||||
66, 67: Freezing Rain: Light and heavy intensity
|
||||
71, 73, 75: Snow fall: Slight, moderate, and heavy intensity
|
||||
77: Snow grains
|
||||
80, 81, 82: Rain showers: Slight, moderate, and violent
|
||||
85, 86: Snow showers slight and heavy
|
||||
95: Thunderstorm: Slight or moderate
|
||||
96, 99: Thunderstorm with slight and heavy hail
|
||||
`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
location: {
|
||||
type: "string",
|
||||
description: "The location to get the weather information",
|
||||
},
|
||||
},
|
||||
required: ["location"],
|
||||
},
|
||||
};
|
||||
|
||||
export class WeatherTool implements BaseTool<WeatherParameter> {
|
||||
metadata: ToolMetadata<JSONSchemaType<WeatherParameter>>;
|
||||
|
||||
private getGeoLocation = async (location: string): Promise<GeoLocation> => {
|
||||
const apiUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${location}&count=10&language=en&format=json`;
|
||||
const response = await fetch(apiUrl);
|
||||
const data = await response.json();
|
||||
const { id, name, latitude, longitude } = data.results[0];
|
||||
return { id, name, latitude, longitude };
|
||||
};
|
||||
|
||||
private getWeatherByLocation = async (location: string) => {
|
||||
console.log(
|
||||
"Calling open-meteo api to get weather information of location:",
|
||||
location,
|
||||
);
|
||||
const { latitude, longitude } = await this.getGeoLocation(location);
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,weather_code&hourly=temperature_2m,weather_code&daily=weather_code&timezone=${timezone}`;
|
||||
const response = await fetch(apiUrl);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
constructor(params?: WeatherToolParams) {
|
||||
this.metadata = params?.metadata || DEFAULT_META_DATA;
|
||||
}
|
||||
|
||||
async call(input: WeatherParameter) {
|
||||
return await this.getWeatherByLocation(input.location);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ContextChatEngine, Settings } from "llamaindex";
|
||||
import { getDataSource } from "./index";
|
||||
|
||||
export async function createChatEngine() {
|
||||
const index = await getDataSource();
|
||||
if (!index) {
|
||||
throw new Error(
|
||||
`StorageContext is empty - call 'npm run generate' to generate the storage first`,
|
||||
);
|
||||
}
|
||||
const retriever = index.asRetriever();
|
||||
retriever.similarityTopK = process.env.TOP_K
|
||||
? parseInt(process.env.TOP_K)
|
||||
: 3;
|
||||
|
||||
return new ContextChatEngine({
|
||||
chatModel: Settings.llm,
|
||||
retriever,
|
||||
systemPrompt: process.env.SYSTEM_PROMPT,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import os
|
||||
import yaml
|
||||
import importlib
|
||||
import logging
|
||||
from typing import Dict
|
||||
from app.engine.loaders.file import FileLoaderConfig, get_file_documents
|
||||
from app.engine.loaders.web import WebLoaderConfig, get_web_documents
|
||||
from app.engine.loaders.db import DBLoaderConfig, get_db_documents
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_configs():
|
||||
with open("config/loaders.yaml") as f:
|
||||
configs = yaml.safe_load(f)
|
||||
return configs
|
||||
|
||||
|
||||
def get_documents():
|
||||
documents = []
|
||||
config = load_configs()
|
||||
for loader_type, loader_config in config.items():
|
||||
logger.info(
|
||||
f"Loading documents from loader: {loader_type}, config: {loader_config}"
|
||||
)
|
||||
match loader_type:
|
||||
case "file":
|
||||
document = get_file_documents(FileLoaderConfig(**loader_config))
|
||||
case "web":
|
||||
document = get_web_documents(WebLoaderConfig(**loader_config))
|
||||
case "db":
|
||||
document = get_db_documents(
|
||||
configs=[DBLoaderConfig(**cfg) for cfg in loader_config]
|
||||
)
|
||||
case _:
|
||||
raise ValueError(f"Invalid loader type: {loader_type}")
|
||||
documents.extend(document)
|
||||
|
||||
return documents
|
||||
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
import logging
|
||||
from typing import List
|
||||
from pydantic import BaseModel, validator
|
||||
from llama_index.core.indices.vector_store import VectorStoreIndex
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DBLoaderConfig(BaseModel):
|
||||
uri: str
|
||||
queries: List[str]
|
||||
|
||||
|
||||
def get_db_documents(configs: list[DBLoaderConfig]):
|
||||
from llama_index.readers.database import DatabaseReader
|
||||
|
||||
docs = []
|
||||
for entry in configs:
|
||||
loader = DatabaseReader(uri=entry.uri)
|
||||
for query in entry.queries:
|
||||
logger.info(f"Loading data from database with query: {query}")
|
||||
documents = loader.load_data(query=query)
|
||||
docs.extend(documents)
|
||||
|
||||
return documents
|
||||
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
import logging
|
||||
from llama_parse import LlamaParse
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FileLoaderConfig(BaseModel):
|
||||
data_dir: str = "data"
|
||||
use_llama_parse: bool = False
|
||||
|
||||
@validator("data_dir")
|
||||
def data_dir_must_exist(cls, v):
|
||||
if not os.path.isdir(v):
|
||||
raise ValueError(f"Directory '{v}' does not exist")
|
||||
return v
|
||||
|
||||
|
||||
def llama_parse_parser():
|
||||
if os.getenv("LLAMA_CLOUD_API_KEY") is None:
|
||||
raise ValueError(
|
||||
"LLAMA_CLOUD_API_KEY environment variable is not set. "
|
||||
"Please set it in .env file or in your shell environment then run again!"
|
||||
)
|
||||
parser = LlamaParse(result_type="markdown", verbose=True, language="en")
|
||||
return parser
|
||||
|
||||
|
||||
def get_file_documents(config: FileLoaderConfig):
|
||||
from llama_index.core.readers import SimpleDirectoryReader
|
||||
|
||||
try:
|
||||
reader = SimpleDirectoryReader(
|
||||
config.data_dir,
|
||||
recursive=True,
|
||||
filename_as_id=True,
|
||||
)
|
||||
if config.use_llama_parse:
|
||||
parser = llama_parse_parser()
|
||||
reader.file_extractor = {".pdf": parser}
|
||||
return reader.load_data()
|
||||
except ValueError as e:
|
||||
import sys, traceback
|
||||
|
||||
# Catch the error if the data dir is empty
|
||||
# and return as empty document list
|
||||
_, _, exc_traceback = sys.exc_info()
|
||||
function_name = traceback.extract_tb(exc_traceback)[-1].name
|
||||
if function_name == "_add_files":
|
||||
logger.warning(
|
||||
f"Failed to load file documents, error message: {e} . Return as empty document list."
|
||||
)
|
||||
return []
|
||||
else:
|
||||
# Raise the error if it is not the case of empty data dir
|
||||
raise e
|
||||
@@ -1,7 +0,0 @@
|
||||
from llama_index.core.readers import SimpleDirectoryReader
|
||||
|
||||
DATA_DIR = "data" # directory containing the documents
|
||||
|
||||
|
||||
def get_documents():
|
||||
return SimpleDirectoryReader(DATA_DIR).load_data()
|
||||
@@ -1,17 +0,0 @@
|
||||
import os
|
||||
from llama_parse import LlamaParse
|
||||
from llama_index.core import SimpleDirectoryReader
|
||||
|
||||
DATA_DIR = "data" # directory containing the documents
|
||||
|
||||
|
||||
def get_documents():
|
||||
if os.getenv("LLAMA_CLOUD_API_KEY") is None:
|
||||
raise ValueError(
|
||||
"LLAMA_CLOUD_API_KEY environment variable is not set. "
|
||||
"Please set it in .env file or in your shell environment then run again!"
|
||||
)
|
||||
parser = LlamaParse(result_type="markdown", verbose=True, language="en")
|
||||
|
||||
reader = SimpleDirectoryReader(DATA_DIR, file_extractor={".pdf": parser})
|
||||
return reader.load_data()
|
||||
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
import json
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CrawlUrl(BaseModel):
|
||||
base_url: str
|
||||
prefix: str
|
||||
max_depth: int = Field(default=1, ge=0)
|
||||
|
||||
|
||||
class WebLoaderConfig(BaseModel):
|
||||
driver_arguments: list[str] = Field(default=None)
|
||||
urls: list[CrawlUrl]
|
||||
|
||||
|
||||
def get_web_documents(config: WebLoaderConfig):
|
||||
from llama_index.readers.web import WholeSiteReader
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
|
||||
options = Options()
|
||||
driver_arguments = config.driver_arguments or []
|
||||
for arg in driver_arguments:
|
||||
options.add_argument(arg)
|
||||
|
||||
docs = []
|
||||
for url in config.urls:
|
||||
scraper = WholeSiteReader(
|
||||
prefix=url.prefix,
|
||||
max_depth=url.max_depth,
|
||||
driver=webdriver.Chrome(options=options),
|
||||
)
|
||||
docs.extend(scraper.load_data(url.base_url))
|
||||
|
||||
return docs
|
||||
@@ -1,13 +0,0 @@
|
||||
import os
|
||||
from llama_index.readers.web import WholeSiteReader
|
||||
|
||||
|
||||
def get_documents():
|
||||
# Initialize the scraper with a prefix URL and maximum depth
|
||||
scraper = WholeSiteReader(
|
||||
prefix=os.environ.get("URL_PREFIX"), max_depth=int(os.environ.get("MAX_DEPTH"))
|
||||
)
|
||||
# Start scraping from a base URL
|
||||
documents = scraper.load_data(base_url=os.environ.get("BASE_URL"))
|
||||
|
||||
return documents
|
||||
@@ -0,0 +1,9 @@
|
||||
import { SimpleDirectoryReader } from "llamaindex";
|
||||
|
||||
export const DATA_DIR = "./data";
|
||||
|
||||
export async function getDocuments() {
|
||||
return await new SimpleDirectoryReader().loadData({
|
||||
directoryPath: DATA_DIR,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
FILE_EXT_TO_READER,
|
||||
LlamaParseReader,
|
||||
SimpleDirectoryReader,
|
||||
} from "llamaindex";
|
||||
|
||||
export const DATA_DIR = "./data";
|
||||
|
||||
export async function getDocuments() {
|
||||
const reader = new SimpleDirectoryReader();
|
||||
// Load PDFs using LlamaParseReader
|
||||
return await reader.loadData({
|
||||
directoryPath: DATA_DIR,
|
||||
fileExtToReader: {
|
||||
...FILE_EXT_TO_READER,
|
||||
pdf: new LlamaParseReader({ resultType: "markdown" }),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
from traceloop.sdk import Traceloop
|
||||
|
||||
|
||||
def init_observability():
|
||||
Traceloop.init()
|
||||
@@ -7,7 +7,7 @@ export default function ChatItem(message: Message) {
|
||||
return (
|
||||
<div className="flex items-start gap-4 pt-5">
|
||||
<ChatAvatar {...message} />
|
||||
<p className="break-words">{message.content}</p>
|
||||
<p className="break-words whitespace-pre-wrap">{message.content}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
from llama_index.vector_stores.astra_db import AstraDBVectorStore
|
||||
|
||||
|
||||
def get_vector_store():
|
||||
endpoint = os.getenv("ASTRA_DB_ENDPOINT")
|
||||
token = os.getenv("ASTRA_DB_APPLICATION_TOKEN")
|
||||
collection = os.getenv("ASTRA_DB_COLLECTION")
|
||||
if not endpoint or not token or not collection:
|
||||
raise ValueError(
|
||||
"Please config ASTRA_DB_ENDPOINT, ASTRA_DB_APPLICATION_TOKEN and ASTRA_DB_COLLECTION"
|
||||
" to your environment variables or config them in the .env file"
|
||||
)
|
||||
store = AstraDBVectorStore(
|
||||
token=token,
|
||||
api_endpoint=endpoint,
|
||||
collection_name=collection,
|
||||
embedding_dimension=int(os.getenv("EMBEDDING_DIM")),
|
||||
)
|
||||
return store
|
||||
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
from llama_index.vector_stores.chroma import ChromaVectorStore
|
||||
|
||||
|
||||
def get_vector_store():
|
||||
collection_name = os.getenv("CHROMA_COLLECTION", "default")
|
||||
chroma_path = os.getenv("CHROMA_PATH")
|
||||
# if CHROMA_PATH is set, use a local ChromaVectorStore from the path
|
||||
# otherwise, use a remote ChromaVectorStore (ChromaDB Cloud is not supported yet)
|
||||
if chroma_path:
|
||||
store = ChromaVectorStore.from_params(
|
||||
persist_dir=chroma_path, collection_name=collection_name
|
||||
)
|
||||
else:
|
||||
if not os.getenv("CHROMA_HOST") or not os.getenv("CHROMA_PORT"):
|
||||
raise ValueError(
|
||||
"Please provide either CHROMA_PATH or CHROMA_HOST and CHROMA_PORT"
|
||||
)
|
||||
store = ChromaVectorStore.from_params(
|
||||
host=os.getenv("CHROMA_HOST"),
|
||||
port=int(os.getenv("CHROMA_PORT")),
|
||||
collection_name=collection_name,
|
||||
)
|
||||
return store
|
||||
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
from llama_index.vector_stores.milvus import MilvusVectorStore
|
||||
|
||||
|
||||
def get_vector_store():
|
||||
address = os.getenv("MILVUS_ADDRESS")
|
||||
collection = os.getenv("MILVUS_COLLECTION")
|
||||
if not address or not collection:
|
||||
raise ValueError(
|
||||
"Please set MILVUS_ADDRESS and MILVUS_COLLECTION to your environment variables"
|
||||
" or config them in the .env file"
|
||||
)
|
||||
store = MilvusVectorStore(
|
||||
uri=address,
|
||||
user=os.getenv("MILVUS_USERNAME"),
|
||||
password=os.getenv("MILVUS_PASSWORD"),
|
||||
collection_name=collection,
|
||||
dim=int(os.getenv("EMBEDDING_DIM")),
|
||||
)
|
||||
return store
|
||||
@@ -1,43 +0,0 @@
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
import os
|
||||
import logging
|
||||
from llama_index.core.storage import StorageContext
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.vector_stores.mongodb import MongoDBAtlasVectorSearch
|
||||
from app.settings import init_settings
|
||||
from app.engine.loader import get_documents
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def generate_datasource():
|
||||
logger.info("Creating new index")
|
||||
# load the documents and create the index
|
||||
documents = get_documents()
|
||||
store = MongoDBAtlasVectorSearch(
|
||||
db_name=os.environ["MONGODB_DATABASE"],
|
||||
collection_name=os.environ["MONGODB_VECTORS"],
|
||||
index_name=os.environ["MONGODB_VECTOR_INDEX"],
|
||||
)
|
||||
storage_context = StorageContext.from_defaults(vector_store=store)
|
||||
VectorStoreIndex.from_documents(
|
||||
documents,
|
||||
storage_context=storage_context,
|
||||
show_progress=True, # this will show you a progress bar as the embeddings are created
|
||||
)
|
||||
logger.info(
|
||||
f"Successfully created embeddings in the MongoDB collection {os.environ['MONGODB_VECTORS']}"
|
||||
)
|
||||
logger.info(
|
||||
"""IMPORTANT: You can't query your index yet because you need to create a vector search index in MongoDB's UI now.
|
||||
See https://github.com/run-llama/mongodb-demo/tree/main?tab=readme-ov-file#create-a-vector-search-index"""
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_settings()
|
||||
generate_datasource()
|
||||
@@ -1,20 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.vector_stores.mongodb import MongoDBAtlasVectorSearch
|
||||
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
|
||||
|
||||
def get_index():
|
||||
logger.info("Connecting to index from MongoDB...")
|
||||
store = MongoDBAtlasVectorSearch(
|
||||
db_name=os.environ["MONGODB_DATABASE"],
|
||||
collection_name=os.environ["MONGODB_VECTORS"],
|
||||
index_name=os.environ["MONGODB_VECTOR_INDEX"],
|
||||
)
|
||||
index = VectorStoreIndex.from_vector_store(store)
|
||||
logger.info("Finished connecting to index from MongoDB.")
|
||||
return index
|
||||
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
from llama_index.vector_stores.mongodb import MongoDBAtlasVectorSearch
|
||||
|
||||
|
||||
def get_vector_store():
|
||||
db_uri = os.getenv("MONGODB_URI")
|
||||
db_name = os.getenv("MONGODB_DATABASE")
|
||||
collection_name = os.getenv("MONGODB_VECTORS")
|
||||
index_name = os.getenv("MONGODB_VECTOR_INDEX")
|
||||
if not db_uri or not db_name or not collection_name or not index_name:
|
||||
raise ValueError(
|
||||
"Please set MONGODB_URI, MONGODB_DATABASE, MONGODB_VECTORS, and MONGODB_VECTOR_INDEX"
|
||||
" to your environment variables or config them in .env file"
|
||||
)
|
||||
store = MongoDBAtlasVectorSearch(
|
||||
db_name=db_name,
|
||||
collection_name=collection_name,
|
||||
index_name=index_name,
|
||||
)
|
||||
return store
|
||||
@@ -1 +0,0 @@
|
||||
STORAGE_DIR = "storage" # directory to cache the generated index
|
||||
@@ -2,12 +2,12 @@ from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
import os
|
||||
import logging
|
||||
from llama_index.core.indices import (
|
||||
VectorStoreIndex,
|
||||
)
|
||||
from app.engine.constants import STORAGE_DIR
|
||||
from app.engine.loader import get_documents
|
||||
from app.engine.loaders import get_documents
|
||||
from app.settings import init_settings
|
||||
|
||||
|
||||
@@ -16,17 +16,18 @@ logger = logging.getLogger()
|
||||
|
||||
|
||||
def generate_datasource():
|
||||
init_settings()
|
||||
logger.info("Creating new index")
|
||||
storage_dir = os.environ.get("STORAGE_DIR", "storage")
|
||||
# load the documents and create the index
|
||||
documents = get_documents()
|
||||
index = VectorStoreIndex.from_documents(
|
||||
documents,
|
||||
)
|
||||
# store it for later
|
||||
index.storage_context.persist(STORAGE_DIR)
|
||||
logger.info(f"Finished creating new index. Stored in {STORAGE_DIR}")
|
||||
index.storage_context.persist(storage_dir)
|
||||
logger.info(f"Finished creating new index. Stored in {storage_dir}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_settings()
|
||||
generate_datasource()
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
import logging
|
||||
import os
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from app.engine.constants import STORAGE_DIR
|
||||
from cachetools import cached, TTLCache
|
||||
from llama_index.core.storage import StorageContext
|
||||
from llama_index.core.indices import load_index_from_storage
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
|
||||
|
||||
def get_index():
|
||||
# check if storage already exists
|
||||
if not os.path.exists(STORAGE_DIR):
|
||||
raise Exception(
|
||||
"StorageContext is empty - call 'python app/engine/generate.py' to generate the storage first"
|
||||
)
|
||||
@cached(
|
||||
TTLCache(maxsize=10, ttl=timedelta(minutes=5).total_seconds()),
|
||||
key=lambda *args, **kwargs: "global_storage_context",
|
||||
)
|
||||
def get_storage_context(persist_dir: str) -> StorageContext:
|
||||
return StorageContext.from_defaults(persist_dir=persist_dir)
|
||||
|
||||
|
||||
def get_index():
|
||||
storage_dir = os.getenv("STORAGE_DIR", "storage")
|
||||
# check if storage already exists
|
||||
if not os.path.exists(storage_dir):
|
||||
return None
|
||||
# load the existing index
|
||||
logger.info(f"Loading index from {STORAGE_DIR}...")
|
||||
storage_context = StorageContext.from_defaults(persist_dir=STORAGE_DIR)
|
||||
logger.info(f"Loading index from {storage_dir}...")
|
||||
storage_context = get_storage_context(storage_dir)
|
||||
index = load_index_from_storage(storage_context)
|
||||
logger.info(f"Finished loading index from {STORAGE_DIR}")
|
||||
logger.info(f"Finished loading index from {storage_dir}")
|
||||
return index
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
PGVECTOR_SCHEMA = "public"
|
||||
PGVECTOR_TABLE = "llamaindex_embedding"
|
||||
@@ -1,35 +0,0 @@
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
import logging
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.core.storage import StorageContext
|
||||
|
||||
from app.engine.loader import get_documents
|
||||
from app.settings import init_settings
|
||||
from app.engine.utils import init_pg_vector_store_from_env
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def generate_datasource():
|
||||
logger.info("Creating new index")
|
||||
# load the documents and create the index
|
||||
documents = get_documents()
|
||||
store = init_pg_vector_store_from_env()
|
||||
storage_context = StorageContext.from_defaults(vector_store=store)
|
||||
VectorStoreIndex.from_documents(
|
||||
documents,
|
||||
storage_context=storage_context,
|
||||
show_progress=True, # this will show you a progress bar as the embeddings are created
|
||||
)
|
||||
logger.info(
|
||||
f"Successfully created embeddings in the PG vector store, schema={store.schema_name} table={store.table_name}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_settings()
|
||||
generate_datasource()
|
||||
@@ -1,13 +0,0 @@
|
||||
import logging
|
||||
from llama_index.core.indices.vector_store import VectorStoreIndex
|
||||
from app.engine.utils import init_pg_vector_store_from_env
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
|
||||
|
||||
def get_index():
|
||||
logger.info("Connecting to index from PGVector...")
|
||||
store = init_pg_vector_store_from_env()
|
||||
index = VectorStoreIndex.from_vector_store(store)
|
||||
logger.info("Finished connecting to index from PGVector.")
|
||||
return index
|
||||
@@ -1,27 +0,0 @@
|
||||
import os
|
||||
from llama_index.vector_stores.postgres import PGVectorStore
|
||||
from urllib.parse import urlparse
|
||||
from app.engine.constants import PGVECTOR_SCHEMA, PGVECTOR_TABLE
|
||||
|
||||
|
||||
def init_pg_vector_store_from_env():
|
||||
original_conn_string = os.environ.get("PG_CONNECTION_STRING")
|
||||
if original_conn_string is None or original_conn_string == "":
|
||||
raise ValueError("PG_CONNECTION_STRING environment variable is not set.")
|
||||
|
||||
# The PGVectorStore requires both two connection strings, one for psycopg2 and one for asyncpg
|
||||
# Update the configured scheme with the psycopg2 and asyncpg schemes
|
||||
original_scheme = urlparse(original_conn_string).scheme + "://"
|
||||
conn_string = original_conn_string.replace(
|
||||
original_scheme, "postgresql+psycopg2://"
|
||||
)
|
||||
async_conn_string = original_conn_string.replace(
|
||||
original_scheme, "postgresql+asyncpg://"
|
||||
)
|
||||
|
||||
return PGVectorStore(
|
||||
connection_string=conn_string,
|
||||
async_connection_string=async_conn_string,
|
||||
schema_name=PGVECTOR_SCHEMA,
|
||||
table_name=PGVECTOR_TABLE,
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
from llama_index.vector_stores.postgres import PGVectorStore
|
||||
from urllib.parse import urlparse
|
||||
|
||||
PGVECTOR_SCHEMA = "public"
|
||||
PGVECTOR_TABLE = "llamaindex_embedding"
|
||||
|
||||
vector_store: PGVectorStore = None
|
||||
|
||||
|
||||
def get_vector_store():
|
||||
global vector_store
|
||||
|
||||
if vector_store is None:
|
||||
original_conn_string = os.environ.get("PG_CONNECTION_STRING")
|
||||
if original_conn_string is None or original_conn_string == "":
|
||||
raise ValueError("PG_CONNECTION_STRING environment variable is not set.")
|
||||
|
||||
# The PGVectorStore requires both two connection strings, one for psycopg2 and one for asyncpg
|
||||
# Update the configured scheme with the psycopg2 and asyncpg schemes
|
||||
original_scheme = urlparse(original_conn_string).scheme + "://"
|
||||
conn_string = original_conn_string.replace(
|
||||
original_scheme, "postgresql+psycopg2://"
|
||||
)
|
||||
async_conn_string = original_conn_string.replace(
|
||||
original_scheme, "postgresql+asyncpg://"
|
||||
)
|
||||
|
||||
vector_store = PGVectorStore(
|
||||
connection_string=conn_string,
|
||||
async_connection_string=async_conn_string,
|
||||
schema_name=PGVECTOR_SCHEMA,
|
||||
table_name=PGVECTOR_TABLE,
|
||||
embed_dim=int(os.environ.get("EMBEDDING_DIM", 1024)),
|
||||
)
|
||||
|
||||
return vector_store
|
||||
@@ -1,39 +0,0 @@
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
import os
|
||||
import logging
|
||||
from llama_index.core.storage import StorageContext
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.vector_stores.pinecone import PineconeVectorStore
|
||||
from app.settings import init_settings
|
||||
from app.engine.loader import get_documents
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def generate_datasource():
|
||||
logger.info("Creating new index")
|
||||
# load the documents and create the index
|
||||
documents = get_documents()
|
||||
store = PineconeVectorStore(
|
||||
api_key=os.environ["PINECONE_API_KEY"],
|
||||
index_name=os.environ["PINECONE_INDEX_NAME"],
|
||||
environment=os.environ["PINECONE_ENVIRONMENT"],
|
||||
)
|
||||
storage_context = StorageContext.from_defaults(vector_store=store)
|
||||
VectorStoreIndex.from_documents(
|
||||
documents,
|
||||
storage_context=storage_context,
|
||||
show_progress=True, # this will show you a progress bar as the embeddings are created
|
||||
)
|
||||
logger.info(
|
||||
f"Successfully created embeddings and save to your Pinecone index {os.environ['PINECONE_INDEX_NAME']}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_settings()
|
||||
generate_datasource()
|
||||
@@ -1,20 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from llama_index.core.indices import VectorStoreIndex
|
||||
from llama_index.vector_stores.pinecone import PineconeVectorStore
|
||||
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
|
||||
|
||||
def get_index():
|
||||
logger.info("Connecting to index from Pinecone...")
|
||||
store = PineconeVectorStore(
|
||||
api_key=os.environ["PINECONE_API_KEY"],
|
||||
index_name=os.environ["PINECONE_INDEX_NAME"],
|
||||
environment=os.environ["PINECONE_ENVIRONMENT"],
|
||||
)
|
||||
index = VectorStoreIndex.from_vector_store(store)
|
||||
logger.info("Finished connecting to index from Pinecone.")
|
||||
return index
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
from llama_index.vector_stores.pinecone import PineconeVectorStore
|
||||
|
||||
|
||||
def get_vector_store():
|
||||
api_key = os.getenv("PINECONE_API_KEY")
|
||||
index_name = os.getenv("PINECONE_INDEX_NAME")
|
||||
environment = os.getenv("PINECONE_ENVIRONMENT")
|
||||
if not api_key or not index_name or not environment:
|
||||
raise ValueError(
|
||||
"Please set PINECONE_API_KEY, PINECONE_INDEX_NAME, and PINECONE_ENVIRONMENT"
|
||||
" to your environment variables or config them in the .env file"
|
||||
)
|
||||
store = PineconeVectorStore(
|
||||
api_key=api_key,
|
||||
index_name=index_name,
|
||||
environment=environment,
|
||||
)
|
||||
return store
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
from llama_index.vector_stores.qdrant import QdrantVectorStore
|
||||
|
||||
|
||||
def get_vector_store():
|
||||
collection_name = os.getenv("QDRANT_COLLECTION")
|
||||
url = os.getenv("QDRANT_URL")
|
||||
api_key = os.getenv("QDRANT_API_KEY")
|
||||
if not collection_name or not url:
|
||||
raise ValueError(
|
||||
"Please set QDRANT_COLLECTION, QDRANT_URL"
|
||||
" to your environment variables or config them in the .env file"
|
||||
)
|
||||
store = QdrantVectorStore(
|
||||
collection_name=collection_name,
|
||||
url=url,
|
||||
api_key=api_key,
|
||||
)
|
||||
return store
|
||||
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import { VectorStoreIndex, storageContextFromDefaults } from "llamaindex";
|
||||
import { AstraDBVectorStore } from "llamaindex/storage/vectorStore/AstraDBVectorStore";
|
||||
import { getDocuments } from "./loader";
|
||||
import { initSettings } from "./settings";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function loadAndIndex() {
|
||||
// load objects from storage and convert them into LlamaIndex Document objects
|
||||
const documents = await getDocuments();
|
||||
|
||||
// create vector store and a collection
|
||||
const collectionName = process.env.ASTRA_DB_COLLECTION!;
|
||||
const vectorStore = new AstraDBVectorStore();
|
||||
await vectorStore.create(collectionName, {
|
||||
vector: {
|
||||
dimension: parseInt(process.env.EMBEDDING_DIM!),
|
||||
metric: "cosine",
|
||||
},
|
||||
});
|
||||
await vectorStore.connect(collectionName);
|
||||
|
||||
// create index from documents and store them in Astra
|
||||
console.log("Start creating embeddings...");
|
||||
const storageContext = await storageContextFromDefaults({ vectorStore });
|
||||
await VectorStoreIndex.fromDocuments(documents, { storageContext });
|
||||
console.log(
|
||||
"Successfully created embeddings and save to your Astra database.",
|
||||
);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
checkRequiredEnvVars();
|
||||
initSettings();
|
||||
await loadAndIndex();
|
||||
console.log("Finished generating storage.");
|
||||
})();
|
||||
@@ -0,0 +1,11 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { VectorStoreIndex } from "llamaindex";
|
||||
import { AstraDBVectorStore } from "llamaindex/storage/vectorStore/AstraDBVectorStore";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
|
||||
export async function getDataSource() {
|
||||
checkRequiredEnvVars();
|
||||
const store = new AstraDBVectorStore();
|
||||
await store.connect(process.env.ASTRA_DB_COLLECTION!);
|
||||
return await VectorStoreIndex.fromVectorStore(store);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
const REQUIRED_ENV_VARS = [
|
||||
"ASTRA_DB_APPLICATION_TOKEN",
|
||||
"ASTRA_DB_ENDPOINT",
|
||||
"ASTRA_DB_COLLECTION",
|
||||
"EMBEDDING_DIM",
|
||||
];
|
||||
|
||||
export function checkRequiredEnvVars() {
|
||||
const missingEnvVars = REQUIRED_ENV_VARS.filter((envVar) => {
|
||||
return !process.env[envVar];
|
||||
});
|
||||
|
||||
if (missingEnvVars.length > 0) {
|
||||
console.log(
|
||||
`The following environment variables are required but missing: ${missingEnvVars.join(
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Missing environment variables: ${missingEnvVars.join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import { VectorStoreIndex, storageContextFromDefaults } from "llamaindex";
|
||||
import { ChromaVectorStore } from "llamaindex/storage/vectorStore/ChromaVectorStore";
|
||||
import { getDocuments } from "./loader";
|
||||
import { initSettings } from "./settings";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function loadAndIndex() {
|
||||
// load objects from storage and convert them into LlamaIndex Document objects
|
||||
const documents = await getDocuments();
|
||||
|
||||
// create vector store
|
||||
const chromaUri = `http://${process.env.CHROMA_HOST}:${process.env.CHROMA_PORT}`;
|
||||
|
||||
const vectorStore = new ChromaVectorStore({
|
||||
collectionName: process.env.CHROMA_COLLECTION,
|
||||
chromaClientParams: { path: chromaUri },
|
||||
});
|
||||
|
||||
// create index from all the Documentss and store them in Pinecone
|
||||
console.log("Start creating embeddings...");
|
||||
const storageContext = await storageContextFromDefaults({ vectorStore });
|
||||
await VectorStoreIndex.fromDocuments(documents, { storageContext });
|
||||
console.log(
|
||||
"Successfully created embeddings and save to your ChromaDB index.",
|
||||
);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
checkRequiredEnvVars();
|
||||
initSettings();
|
||||
await loadAndIndex();
|
||||
console.log("Finished generating storage.");
|
||||
})();
|
||||
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { VectorStoreIndex } from "llamaindex";
|
||||
import { ChromaVectorStore } from "llamaindex/storage/vectorStore/ChromaVectorStore";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
|
||||
export async function getDataSource() {
|
||||
checkRequiredEnvVars();
|
||||
const chromaUri = `http://${process.env.CHROMA_HOST}:${process.env.CHROMA_PORT}`;
|
||||
|
||||
const store = new ChromaVectorStore({
|
||||
collectionName: process.env.CHROMA_COLLECTION,
|
||||
chromaClientParams: { path: chromaUri },
|
||||
});
|
||||
|
||||
return await VectorStoreIndex.fromVectorStore(store);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
const REQUIRED_ENV_VARS = ["CHROMA_COLLECTION", "CHROMA_HOST", "CHROMA_PORT"];
|
||||
|
||||
export function checkRequiredEnvVars() {
|
||||
const missingEnvVars = REQUIRED_ENV_VARS.filter((envVar) => {
|
||||
return !process.env[envVar];
|
||||
});
|
||||
|
||||
if (missingEnvVars.length > 0) {
|
||||
console.log(
|
||||
`The following environment variables are required but missing: ${missingEnvVars.join(
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Missing environment variables: ${missingEnvVars.join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import { VectorStoreIndex, storageContextFromDefaults } from "llamaindex";
|
||||
import { MilvusVectorStore } from "llamaindex/storage/vectorStore/MilvusVectorStore";
|
||||
import { getDocuments } from "./loader";
|
||||
import { initSettings } from "./settings";
|
||||
import { checkRequiredEnvVars, getMilvusClient } from "./shared";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const collectionName = process.env.MILVUS_COLLECTION;
|
||||
|
||||
async function loadAndIndex() {
|
||||
// load objects from storage and convert them into LlamaIndex Document objects
|
||||
const documents = await getDocuments();
|
||||
|
||||
// Connect to Milvus
|
||||
const milvusClient = getMilvusClient();
|
||||
const vectorStore = new MilvusVectorStore({ milvusClient });
|
||||
|
||||
// now create an index from all the Documents and store them in Milvus
|
||||
const storageContext = await storageContextFromDefaults({ vectorStore });
|
||||
await VectorStoreIndex.fromDocuments(documents, {
|
||||
storageContext: storageContext,
|
||||
});
|
||||
console.log(
|
||||
`Successfully created embeddings in the Milvus collection ${collectionName}.`,
|
||||
);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
checkRequiredEnvVars();
|
||||
initSettings();
|
||||
await loadAndIndex();
|
||||
console.log("Finished generating storage.");
|
||||
})();
|
||||
@@ -0,0 +1,11 @@
|
||||
import { VectorStoreIndex } from "llamaindex";
|
||||
import { MilvusVectorStore } from "llamaindex/storage/vectorStore/MilvusVectorStore";
|
||||
import { checkRequiredEnvVars, getMilvusClient } from "./shared";
|
||||
|
||||
export async function getDataSource() {
|
||||
checkRequiredEnvVars();
|
||||
const milvusClient = getMilvusClient();
|
||||
const store = new MilvusVectorStore({ milvusClient });
|
||||
|
||||
return await VectorStoreIndex.fromVectorStore(store);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { MilvusClient } from "@zilliz/milvus2-sdk-node";
|
||||
|
||||
const REQUIRED_ENV_VARS = [
|
||||
"MILVUS_ADDRESS",
|
||||
"MILVUS_USERNAME",
|
||||
"MILVUS_PASSWORD",
|
||||
"MILVUS_COLLECTION",
|
||||
];
|
||||
|
||||
export function getMilvusClient() {
|
||||
const milvusAddress = process.env.MILVUS_ADDRESS;
|
||||
if (!milvusAddress) {
|
||||
throw new Error("MILVUS_ADDRESS environment variable is required");
|
||||
}
|
||||
return new MilvusClient({
|
||||
address: process.env.MILVUS_ADDRESS!,
|
||||
username: process.env.MILVUS_USERNAME,
|
||||
password: process.env.MILVUS_PASSWORD,
|
||||
});
|
||||
}
|
||||
|
||||
export function checkRequiredEnvVars() {
|
||||
const missingEnvVars = REQUIRED_ENV_VARS.filter((envVar) => {
|
||||
return !process.env[envVar];
|
||||
});
|
||||
|
||||
if (missingEnvVars.length > 0) {
|
||||
console.log(
|
||||
`The following environment variables are required but missing: ${missingEnvVars.join(
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Missing environment variables: ${missingEnvVars.join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
-13
@@ -1,19 +1,17 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import {
|
||||
MongoDBAtlasVectorSearch,
|
||||
SimpleDirectoryReader,
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { VectorStoreIndex, storageContextFromDefaults } from "llamaindex";
|
||||
import { MongoDBAtlasVectorSearch } from "llamaindex/storage/vectorStore/MongoDBAtlasVectorSearch";
|
||||
import { MongoClient } from "mongodb";
|
||||
import { STORAGE_DIR, checkRequiredEnvVars } from "./shared.mjs";
|
||||
import { getDocuments } from "./loader";
|
||||
import { initSettings } from "./settings";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const mongoUri = process.env.MONGO_URI;
|
||||
const databaseName = process.env.MONGODB_DATABASE;
|
||||
const vectorCollectionName = process.env.MONGODB_VECTORS;
|
||||
const mongoUri = process.env.MONGODB_URI!;
|
||||
const databaseName = process.env.MONGODB_DATABASE!;
|
||||
const vectorCollectionName = process.env.MONGODB_VECTORS!;
|
||||
const indexName = process.env.MONGODB_VECTOR_INDEX;
|
||||
|
||||
async function loadAndIndex() {
|
||||
@@ -21,9 +19,7 @@ async function loadAndIndex() {
|
||||
const client = new MongoClient(mongoUri);
|
||||
|
||||
// load objects from storage and convert them into LlamaIndex Document objects
|
||||
const documents = await new SimpleDirectoryReader().loadData({
|
||||
directoryPath: STORAGE_DIR,
|
||||
});
|
||||
const documents = await getDocuments();
|
||||
|
||||
// create Atlas as a vector store
|
||||
const vectorStore = new MongoDBAtlasVectorSearch({
|
||||
@@ -44,6 +40,7 @@ async function loadAndIndex() {
|
||||
|
||||
(async () => {
|
||||
checkRequiredEnvVars();
|
||||
initSettings();
|
||||
await loadAndIndex();
|
||||
console.log("Finished generating storage.");
|
||||
})();
|
||||
@@ -1,37 +1,18 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import {
|
||||
ContextChatEngine,
|
||||
LLM,
|
||||
MongoDBAtlasVectorSearch,
|
||||
serviceContextFromDefaults,
|
||||
VectorStoreIndex,
|
||||
} from "llamaindex";
|
||||
import { VectorStoreIndex } from "llamaindex";
|
||||
import { MongoDBAtlasVectorSearch } from "llamaindex/storage/vectorStore/MongoDBAtlasVectorSearch";
|
||||
import { MongoClient } from "mongodb";
|
||||
import { checkRequiredEnvVars, CHUNK_OVERLAP, CHUNK_SIZE } from "./shared.mjs";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
|
||||
async function getDataSource(llm: LLM) {
|
||||
export async function getDataSource() {
|
||||
checkRequiredEnvVars();
|
||||
const client = new MongoClient(process.env.MONGO_URI!);
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
llm,
|
||||
chunkSize: CHUNK_SIZE,
|
||||
chunkOverlap: CHUNK_OVERLAP,
|
||||
});
|
||||
const store = new MongoDBAtlasVectorSearch({
|
||||
mongodbClient: client,
|
||||
dbName: process.env.MONGODB_DATABASE,
|
||||
collectionName: process.env.MONGODB_VECTORS,
|
||||
dbName: process.env.MONGODB_DATABASE!,
|
||||
collectionName: process.env.MONGODB_VECTORS!,
|
||||
indexName: process.env.MONGODB_VECTOR_INDEX,
|
||||
});
|
||||
|
||||
return await VectorStoreIndex.fromVectorStore(store, serviceContext);
|
||||
}
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever({ similarityTopK: 3 });
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
return await VectorStoreIndex.fromVectorStore(store);
|
||||
}
|
||||
|
||||
+1
-5
@@ -1,9 +1,5 @@
|
||||
export const STORAGE_DIR = "./data";
|
||||
export const CHUNK_SIZE = 512;
|
||||
export const CHUNK_OVERLAP = 20;
|
||||
|
||||
const REQUIRED_ENV_VARS = [
|
||||
"MONGO_URI",
|
||||
"MONGODB_URI",
|
||||
"MONGODB_DATABASE",
|
||||
"MONGODB_VECTORS",
|
||||
"MONGODB_VECTOR_INDEX",
|
||||
@@ -1,4 +0,0 @@
|
||||
export const STORAGE_DIR = "./data";
|
||||
export const STORAGE_CACHE_DIR = "./cache";
|
||||
export const CHUNK_SIZE = 512;
|
||||
export const CHUNK_OVERLAP = 20;
|
||||
+10
-24
@@ -1,53 +1,39 @@
|
||||
import {
|
||||
serviceContextFromDefaults,
|
||||
SimpleDirectoryReader,
|
||||
storageContextFromDefaults,
|
||||
VectorStoreIndex,
|
||||
} from "llamaindex";
|
||||
import { VectorStoreIndex } from "llamaindex";
|
||||
import { storageContextFromDefaults } from "llamaindex/storage/StorageContext";
|
||||
|
||||
import * as dotenv from "dotenv";
|
||||
|
||||
import {
|
||||
CHUNK_OVERLAP,
|
||||
CHUNK_SIZE,
|
||||
STORAGE_CACHE_DIR,
|
||||
STORAGE_DIR,
|
||||
} from "./constants.mjs";
|
||||
import { getDocuments } from "./loader";
|
||||
import { initSettings } from "./settings";
|
||||
import { STORAGE_CACHE_DIR } from "./shared";
|
||||
|
||||
// Load environment variables from local .env file
|
||||
dotenv.config();
|
||||
|
||||
async function getRuntime(func) {
|
||||
async function getRuntime(func: any) {
|
||||
const start = Date.now();
|
||||
await func();
|
||||
const end = Date.now();
|
||||
return end - start;
|
||||
}
|
||||
|
||||
async function generateDatasource(serviceContext) {
|
||||
async function generateDatasource() {
|
||||
console.log(`Generating storage context...`);
|
||||
// Split documents, create embeddings and store them in the storage context
|
||||
const ms = await getRuntime(async () => {
|
||||
const storageContext = await storageContextFromDefaults({
|
||||
persistDir: STORAGE_CACHE_DIR,
|
||||
});
|
||||
const documents = await new SimpleDirectoryReader().loadData({
|
||||
directoryPath: STORAGE_DIR,
|
||||
});
|
||||
const documents = await getDocuments();
|
||||
await VectorStoreIndex.fromDocuments(documents, {
|
||||
storageContext,
|
||||
serviceContext,
|
||||
});
|
||||
});
|
||||
console.log(`Storage context successfully generated in ${ms / 1000}s.`);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
chunkSize: CHUNK_SIZE,
|
||||
chunkOverlap: CHUNK_OVERLAP,
|
||||
});
|
||||
|
||||
await generateDatasource(serviceContext);
|
||||
initSettings();
|
||||
await generateDatasource();
|
||||
console.log("Finished generating storage.");
|
||||
})();
|
||||
@@ -1,19 +1,8 @@
|
||||
import {
|
||||
ContextChatEngine,
|
||||
LLM,
|
||||
serviceContextFromDefaults,
|
||||
SimpleDocumentStore,
|
||||
storageContextFromDefaults,
|
||||
VectorStoreIndex,
|
||||
} from "llamaindex";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, STORAGE_CACHE_DIR } from "./constants.mjs";
|
||||
import { SimpleDocumentStore, VectorStoreIndex } from "llamaindex";
|
||||
import { storageContextFromDefaults } from "llamaindex/storage/StorageContext";
|
||||
import { STORAGE_CACHE_DIR } from "./shared";
|
||||
|
||||
async function getDataSource(llm: LLM) {
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
llm,
|
||||
chunkSize: CHUNK_SIZE,
|
||||
chunkOverlap: CHUNK_OVERLAP,
|
||||
});
|
||||
export async function getDataSource() {
|
||||
const storageContext = await storageContextFromDefaults({
|
||||
persistDir: `${STORAGE_CACHE_DIR}`,
|
||||
});
|
||||
@@ -22,23 +11,9 @@ async function getDataSource(llm: LLM) {
|
||||
(storageContext.docStore as SimpleDocumentStore).toDict(),
|
||||
).length;
|
||||
if (numberOfDocs === 0) {
|
||||
throw new Error(
|
||||
`StorageContext is empty - call 'npm run generate' to generate the storage first`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return await VectorStoreIndex.init({
|
||||
storageContext,
|
||||
serviceContext,
|
||||
});
|
||||
}
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever();
|
||||
retriever.similarityTopK = 3;
|
||||
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const STORAGE_CACHE_DIR = "./cache";
|
||||
+9
-12
@@ -1,25 +1,21 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import { VectorStoreIndex, storageContextFromDefaults } from "llamaindex";
|
||||
import { PGVectorStore } from "llamaindex/storage/vectorStore/PGVectorStore";
|
||||
import { getDocuments } from "./loader";
|
||||
import { initSettings } from "./settings";
|
||||
import {
|
||||
PGVectorStore,
|
||||
SimpleDirectoryReader,
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import {
|
||||
PGVECTOR_COLLECTION,
|
||||
PGVECTOR_SCHEMA,
|
||||
PGVECTOR_TABLE,
|
||||
STORAGE_DIR,
|
||||
checkRequiredEnvVars,
|
||||
} from "./shared.mjs";
|
||||
} from "./shared";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function loadAndIndex() {
|
||||
// load objects from storage and convert them into LlamaIndex Document objects
|
||||
const documents = await new SimpleDirectoryReader().loadData({
|
||||
directoryPath: STORAGE_DIR,
|
||||
});
|
||||
const documents = await getDocuments();
|
||||
|
||||
// create postgres vector store
|
||||
const vectorStore = new PGVectorStore({
|
||||
@@ -27,7 +23,7 @@ async function loadAndIndex() {
|
||||
schemaName: PGVECTOR_SCHEMA,
|
||||
tableName: PGVECTOR_TABLE,
|
||||
});
|
||||
vectorStore.setCollection(STORAGE_DIR);
|
||||
vectorStore.setCollection(PGVECTOR_COLLECTION);
|
||||
vectorStore.clearCollection();
|
||||
|
||||
// create index from all the Documents
|
||||
@@ -39,6 +35,7 @@ async function loadAndIndex() {
|
||||
|
||||
(async () => {
|
||||
checkRequiredEnvVars();
|
||||
initSettings();
|
||||
await loadAndIndex();
|
||||
console.log("Finished generating storage.");
|
||||
process.exit(0);
|
||||
@@ -1,39 +1,18 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { VectorStoreIndex } from "llamaindex";
|
||||
import { PGVectorStore } from "llamaindex/storage/vectorStore/PGVectorStore";
|
||||
import {
|
||||
ContextChatEngine,
|
||||
LLM,
|
||||
PGVectorStore,
|
||||
VectorStoreIndex,
|
||||
serviceContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import {
|
||||
CHUNK_OVERLAP,
|
||||
CHUNK_SIZE,
|
||||
PGVECTOR_SCHEMA,
|
||||
PGVECTOR_TABLE,
|
||||
checkRequiredEnvVars,
|
||||
} from "./shared.mjs";
|
||||
} from "./shared";
|
||||
|
||||
async function getDataSource(llm: LLM) {
|
||||
export async function getDataSource() {
|
||||
checkRequiredEnvVars();
|
||||
const pgvs = new PGVectorStore({
|
||||
connectionString: process.env.PG_CONNECTION_STRING,
|
||||
schemaName: PGVECTOR_SCHEMA,
|
||||
tableName: PGVECTOR_TABLE,
|
||||
});
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
llm,
|
||||
chunkSize: CHUNK_SIZE,
|
||||
chunkOverlap: CHUNK_OVERLAP,
|
||||
});
|
||||
return await VectorStoreIndex.fromVectorStore(pgvs, serviceContext);
|
||||
}
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever({ similarityTopK: 3 });
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
return await VectorStoreIndex.fromVectorStore(pgvs);
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,10 +1,8 @@
|
||||
export const STORAGE_DIR = "./data";
|
||||
export const CHUNK_SIZE = 512;
|
||||
export const CHUNK_OVERLAP = 20;
|
||||
export const PGVECTOR_COLLECTION = "data";
|
||||
export const PGVECTOR_SCHEMA = "public";
|
||||
export const PGVECTOR_TABLE = "llamaindex_embedding";
|
||||
|
||||
const REQUIRED_ENV_VARS = ["PG_CONNECTION_STRING", "OPENAI_API_KEY"];
|
||||
const REQUIRED_ENV_VARS = ["PG_CONNECTION_STRING"];
|
||||
|
||||
export function checkRequiredEnvVars() {
|
||||
const missingEnvVars = REQUIRED_ENV_VARS.filter((envVar) => {
|
||||
+7
-10
@@ -1,20 +1,16 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import * as dotenv from "dotenv";
|
||||
import {
|
||||
PineconeVectorStore,
|
||||
SimpleDirectoryReader,
|
||||
VectorStoreIndex,
|
||||
storageContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { STORAGE_DIR, checkRequiredEnvVars } from "./shared.mjs";
|
||||
import { VectorStoreIndex, storageContextFromDefaults } from "llamaindex";
|
||||
import { PineconeVectorStore } from "llamaindex/storage/vectorStore/PineconeVectorStore";
|
||||
import { getDocuments } from "./loader";
|
||||
import { initSettings } from "./settings";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function loadAndIndex() {
|
||||
// load objects from storage and convert them into LlamaIndex Document objects
|
||||
const documents = await new SimpleDirectoryReader().loadData({
|
||||
directoryPath: STORAGE_DIR,
|
||||
});
|
||||
const documents = await getDocuments();
|
||||
|
||||
// create vector store
|
||||
const vectorStore = new PineconeVectorStore();
|
||||
@@ -30,6 +26,7 @@ async function loadAndIndex() {
|
||||
|
||||
(async () => {
|
||||
checkRequiredEnvVars();
|
||||
initSettings();
|
||||
await loadAndIndex();
|
||||
console.log("Finished generating storage.");
|
||||
})();
|
||||
@@ -1,29 +1,10 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import {
|
||||
ContextChatEngine,
|
||||
LLM,
|
||||
PineconeVectorStore,
|
||||
VectorStoreIndex,
|
||||
serviceContextFromDefaults,
|
||||
} from "llamaindex";
|
||||
import { CHUNK_OVERLAP, CHUNK_SIZE, checkRequiredEnvVars } from "./shared.mjs";
|
||||
import { VectorStoreIndex } from "llamaindex";
|
||||
import { PineconeVectorStore } from "llamaindex/storage/vectorStore/PineconeVectorStore";
|
||||
import { checkRequiredEnvVars } from "./shared";
|
||||
|
||||
async function getDataSource(llm: LLM) {
|
||||
export async function getDataSource() {
|
||||
checkRequiredEnvVars();
|
||||
const serviceContext = serviceContextFromDefaults({
|
||||
llm,
|
||||
chunkSize: CHUNK_SIZE,
|
||||
chunkOverlap: CHUNK_OVERLAP,
|
||||
});
|
||||
const store = new PineconeVectorStore();
|
||||
return await VectorStoreIndex.fromVectorStore(store, serviceContext);
|
||||
}
|
||||
|
||||
export async function createChatEngine(llm: LLM) {
|
||||
const index = await getDataSource(llm);
|
||||
const retriever = index.asRetriever({ similarityTopK: 5 });
|
||||
return new ContextChatEngine({
|
||||
chatModel: llm,
|
||||
retriever,
|
||||
});
|
||||
return await VectorStoreIndex.fromVectorStore(store);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user