mirror of
https://github.com/run-llama/create-llama.git
synced 2026-07-02 19:14:28 -04:00
Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc56fa3c5f | |||
| 087c96164d | |||
| 3ff0a18876 | |||
| df1047480a | |||
| 8d89223a08 | |||
| 49a944182f | |||
| 058b3762c1 | |||
| 4c8579b04f | |||
| bb1e82cdae | |||
| f682a1c36e | |||
| b8a1ff6412 | |||
| 5fe9e17d3f | |||
| 15619d81a6 | |||
| 76742da78a | |||
| 693d7a0ea5 | |||
| 8d59ef0a6b | |||
| c62f26e31c | |||
| d3f73679b4 | |||
| 91c35cff33 | |||
| 82ac925224 | |||
| f24ee8e6f9 | |||
| 3acec88fbc | |||
| eee3230e99 | |||
| d8425e5290 | |||
| 0bc5a0d882 | |||
| bbae802bed | |||
| 25fba4381b | |||
| d0618fa2fa | |||
| f3fe3ffc9b | |||
| 6f75d4ab6e | |||
| 3242738fe4 | |||
| 17538eb0dd | |||
| d3772cb4a2 | |||
| 527075c086 | |||
| fb7d4da149 | |||
| 5c35b194bb | |||
| 85e5e7e662 | |||
| 58362542c0 | |||
| 6f44185f68 | |||
| afe9e9fc16 | |||
| 1b5a519f13 | |||
| f072308d03 | |||
| 1df8cfbdc2 | |||
| 24515393a6 | |||
| b3eb0ba7d4 | |||
| 556f33c0ab | |||
| 7a70390b00 | |||
| ad5912b41f | |||
| 76502d28e7 | |||
| f4ca602da5 | |||
| d304554f33 | |||
| 8dce9f913d | |||
| c62096c516 | |||
| 0384268543 | |||
| d9f9e3c1c3 | |||
| 1357c423a3 | |||
| 8105aa70b6 | |||
| 23a90625d1 | |||
| ac789bcb8d | |||
| 241d82a87d | |||
| b16cfd873b | |||
| 3130cdf18d | |||
| 7711216134 | |||
| 93d601972e | |||
| 8fe5fc24c1 | |||
| 3960618454 | |||
| 53e1cd56e7 | |||
| 0a2e12a2bb | |||
| 2e536dca36 | |||
| 4bc53ac24e | |||
| 2deb63a6cc | |||
| 2ffa057f77 | |||
| 64f151dd66 | |||
| 765181adb0 | |||
| 95c35e8a5c | |||
| 598865768a | |||
| 05453d55bf | |||
| d363ced4d8 | |||
| 293c6f97c1 | |||
| 44b4d89ac1 | |||
| 60f10c5b5d | |||
| ee85320701 | |||
| b12dc6f1e8 | |||
| 7c3b279417 | |||
| 1514a555d5 | |||
| cddb4f6bcc | |||
| c82e4f5791 | |||
| 1f7e0e3c69 | |||
| 7997cdeb70 | |||
| 76ec3605e5 | |||
| 5cfdec7d75 | |||
| 3d1b15d515 | |||
| 392393af9e | |||
| 920beda8ad | |||
| e6f8add778 | |||
| c9f8f8d5f2 | |||
| 24eb7736ee | |||
| 5fb27220f7 | |||
| 5caa3813f8 | |||
| bc95789a8d | |||
| 08b3e079e4 | |||
| 1876950f89 | |||
| c7349b44c4 | |||
| 4068618b2d | |||
| 54c9e2f95e | |||
| aec1173b71 | |||
| 481663dd63 | |||
| 1ca7dd2e48 | |||
| 3d20990713 | |||
| 8fb69cf807 | |||
| 61af56dac6 | |||
| 4b66039a96 | |||
| ee88f681a6 | |||
| 992c3a95e9 | |||
| 2a4fb702d1 | |||
| 24b9337096 | |||
| fceec69a3a | |||
| 03e5e0a16e | |||
| fe3cd36d3a | |||
| d5d10e9ead | |||
| 5ed925d75f | |||
| ca5df14d41 | |||
| ee69ce7cc1 | |||
| 0e4ecfaf8b | |||
| 3658fec684 | |||
| c3d275abe1 | |||
| 61204a1381 | |||
| 9e723c3a15 | |||
| d5da55b993 | |||
| c1552ebb00 | |||
| 131e63ae4a | |||
| 4e06714cdd | |||
| 18c8d2540c | |||
| d4b4338f54 | |||
| b4e41aa526 | |||
| 860b9d46d4 | |||
| f73d46bf10 | |||
| eec237c5fe | |||
| 5450096e96 | |||
| 163492f189 | |||
| a84743c576 | |||
| fc5e56efa5 | |||
| a7a6592441 | |||
| af21426952 | |||
| 9077cae2f5 | |||
| 765d2c4fff | |||
| 25667d45e9 | |||
| d31910a303 | |||
| 9852e7399c | |||
| 95227a7539 | |||
| 71f29ea85d | |||
| 27d2499aff | |||
| a07f320e6d | |||
| f9a057ddde | |||
| aedd73d8c0 | |||
| da4505aff7 | |||
| 63e961e635 | |||
| fe90a7e7ee | |||
| 02b2473103 | |||
| f17449b90a | |||
| 28c8808ce3 | |||
| 0a7dfcf84b | |||
| 6e70e327d3 | |||
| 8b371d8347 | |||
| 30fe269575 | |||
| 49c35b834b | |||
| 82c2580ee5 | |||
| fc5b266a40 | |||
| f8f97d2c00 | |||
| 9c2e094883 | |||
| 00f0b3ae03 | |||
| 4663dec81d | |||
| 7f14e47f56 | |||
| 6925676013 | |||
| 44b34fb464 | |||
| a108911fc1 | |||
| 282eaa07fc | |||
| 80db5f7c46 | |||
| 7a22c9f56d | |||
| 8431b788ad | |||
| 2b712cebec | |||
| 6edea6af5c | |||
| d79d1652d1 | |||
| 8ebd8d7039 | |||
| 2b8aaa835d | |||
| 1fe21f85bd | |||
| b9570b2eb9 | |||
| 00009ae53e | |||
| 63558c11fa | |||
| 9172fed2e8 | |||
| 78ccde78fc | |||
| 02510703d8 | |||
| ed59927bd0 | |||
| 9f866aa981 | |||
| b8f78612b8 | |||
| 4a8346900d | |||
| 42e63842d0 | |||
| fa803787e3 | |||
| c5559d8e59 | |||
| 0182368744 | |||
| ff46bd6153 | |||
| 2209409cdb | |||
| 623f8b811b | |||
| 384a1368dd | |||
| 189c0e3f6c | |||
| 99b8247bc9 | |||
| 74c5a15450 | |||
| 9293e330ac | |||
| 6d1b6b9372 | |||
| a8162a9269 | |||
| f3577c50d6 | |||
| a5f5c9dc9c | |||
| 2be68d1c7f | |||
| 8c80cc05ce | |||
| dfd4fd58ab | |||
| 0a69fe09fa | |||
| de88b32208 | |||
| ef88bff211 | |||
| 7562cb48d6 | |||
| 9dde6d0288 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
docs: chroma env variables
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"max-params": [
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"prefer-const": "error",
|
||||
},
|
||||
}
|
||||
+66
-29
@@ -1,12 +1,15 @@
|
||||
name: E2E Tests
|
||||
name: E2E Tests for create-llama package
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- "python/llama-index-server/**"
|
||||
- ".github/workflows/*llama_index_server.yml"
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.6.1"
|
||||
paths-ignore:
|
||||
- "python/llama-index-server/**"
|
||||
- ".github/workflows/*llama_index_server.yml"
|
||||
|
||||
jobs:
|
||||
e2e-python:
|
||||
@@ -20,6 +23,7 @@ jobs:
|
||||
os: [macos-latest, windows-latest, ubuntu-22.04]
|
||||
frameworks: ["fastapi"]
|
||||
datasources: ["--no-files", "--example-file", "--llamacloud"]
|
||||
template-types: ["streaming", "llamaindexserver"]
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@@ -32,10 +36,10 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Add uv to PATH # Ensure uv is available in subsequent steps
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
@@ -50,15 +54,24 @@ jobs:
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
working-directory: .
|
||||
working-directory: packages/create-llama
|
||||
|
||||
- name: Build create-llama
|
||||
run: pnpm run build
|
||||
working-directory: .
|
||||
working-directory: packages/create-llama
|
||||
|
||||
- name: Install
|
||||
run: pnpm run pack-install
|
||||
working-directory: .
|
||||
working-directory: packages/create-llama
|
||||
|
||||
- name: Build and store server package
|
||||
run: |
|
||||
pnpm run build
|
||||
wheel_file=$(ls dist/*.whl | head -n 1)
|
||||
mkdir -p "${{ runner.temp }}"
|
||||
cp "$wheel_file" "${{ runner.temp }}/"
|
||||
echo "SERVER_PACKAGE_PATH=${{ runner.temp }}/$(basename "$wheel_file")" >> $GITHUB_ENV
|
||||
working-directory: python/llama-index-server
|
||||
|
||||
- name: Run Playwright tests for Python
|
||||
run: pnpm run e2e:python
|
||||
@@ -67,13 +80,18 @@ jobs:
|
||||
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
|
||||
FRAMEWORK: ${{ matrix.frameworks }}
|
||||
DATASOURCE: ${{ matrix.datasources }}
|
||||
working-directory: .
|
||||
TEMPLATE_TYPE: ${{ matrix.template-types }}
|
||||
PYTHONIOENCODING: utf-8
|
||||
PYTHONLEGACYWINDOWSSTDIO: utf-8
|
||||
SERVER_PACKAGE_PATH: ${{ env.SERVER_PACKAGE_PATH }}
|
||||
working-directory: packages/create-llama
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-python
|
||||
path: ./playwright-report/
|
||||
name: playwright-report-python-${{ matrix.os }}-${{ matrix.frameworks }}-${{ matrix.datasources }}-${{ matrix.template-types }}
|
||||
path: packages/create-llama/playwright-report/
|
||||
overwrite: true
|
||||
retention-days: 30
|
||||
|
||||
e2e-typescript:
|
||||
@@ -82,11 +100,12 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
node-version: [18, 20]
|
||||
node-version: [20, 22]
|
||||
python-version: ["3.11"]
|
||||
os: [macos-latest, windows-latest, ubuntu-22.04]
|
||||
frameworks: ["nextjs", "express"]
|
||||
datasources: ["--no-files", "--example-file"]
|
||||
frameworks: ["nextjs"]
|
||||
datasources: ["--no-files", "--example-file", "--llamacloud"]
|
||||
template-types: ["streaming", "llamaindexserver"]
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@@ -99,10 +118,10 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Add uv to PATH # Ensure uv is available in subsequent steps
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
@@ -117,15 +136,30 @@ jobs:
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
working-directory: .
|
||||
working-directory: packages/create-llama
|
||||
|
||||
- name: Build create-llama
|
||||
run: pnpm run build
|
||||
working-directory: .
|
||||
working-directory: packages/create-llama
|
||||
|
||||
- name: Install
|
||||
run: pnpm run pack-install
|
||||
working-directory: .
|
||||
working-directory: packages/create-llama
|
||||
|
||||
- name: Build server
|
||||
run: pnpm run build
|
||||
working-directory: packages/server
|
||||
|
||||
- name: Pack @llamaindex/server package
|
||||
run: |
|
||||
pnpm pack --pack-destination "${{ runner.temp }}"
|
||||
if [ "${{ runner.os }}" == "Windows" ]; then
|
||||
file=$(find "${{ runner.temp }}" -name "llamaindex-server-*.tgz" | head -n 1)
|
||||
mv "$file" "${{ runner.temp }}/llamaindex-server.tgz"
|
||||
else
|
||||
mv ${{ runner.temp }}/llamaindex-server-*.tgz ${{ runner.temp }}/llamaindex-server.tgz
|
||||
fi
|
||||
working-directory: packages/server
|
||||
|
||||
- name: Run Playwright tests for TypeScript
|
||||
run: pnpm run e2e:typescript
|
||||
@@ -134,11 +168,14 @@ jobs:
|
||||
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
|
||||
FRAMEWORK: ${{ matrix.frameworks }}
|
||||
DATASOURCE: ${{ matrix.datasources }}
|
||||
working-directory: .
|
||||
TEMPLATE_TYPE: ${{ matrix.template-types }}
|
||||
SERVER_PACKAGE_PATH: ${{ runner.temp }}/llamaindex-server.tgz
|
||||
working-directory: packages/create-llama
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-typescript
|
||||
path: ./playwright-report/
|
||||
name: playwright-report-typescript-${{ matrix.os }}-${{ matrix.frameworks }}-${{ matrix.datasources }}-node${{ matrix.node-version }}-${{ matrix.template-types }}
|
||||
path: packages/create-llama/playwright-report/
|
||||
overwrite: true
|
||||
retention-days: 30
|
||||
|
||||
@@ -16,6 +16,16 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -31,12 +41,21 @@ jobs:
|
||||
- name: Run Prettier
|
||||
run: pnpm run format
|
||||
|
||||
- name: Run build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Run Typecheck for examples
|
||||
run: pnpm run typecheck
|
||||
working-directory: packages/server/examples
|
||||
|
||||
- name: Run Python format check
|
||||
uses: chartboost/ruff-action@v1
|
||||
with:
|
||||
args: "format --check"
|
||||
src: "python/llama-index-server"
|
||||
|
||||
- name: Run Python lint
|
||||
uses: chartboost/ruff-action@v1
|
||||
with:
|
||||
args: "check"
|
||||
src: "python/llama-index-server"
|
||||
|
||||
@@ -17,6 +17,11 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
|
||||
@@ -51,8 +56,12 @@ jobs:
|
||||
with:
|
||||
commit: Release ${{ steps.get-changeset-status.outputs.new-version }}
|
||||
title: Release ${{ steps.get-changeset-status.outputs.new-version }}
|
||||
# bump versions
|
||||
version: pnpm new-version
|
||||
# build package and call changeset publish
|
||||
publish: pnpm release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
||||
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
name: Build Package
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.9"
|
||||
UI_TEST: "true"
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
name: Unit Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python/llama-index-server
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
python-version: ["3.9"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pnpm install && pnpm build
|
||||
|
||||
- name: Run unit tests
|
||||
shell: bash
|
||||
run: uv run pytest tests
|
||||
|
||||
type-check:
|
||||
name: Type Check
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python/llama-index-server
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Run mypy
|
||||
shell: bash
|
||||
run: uv run mypy llama_index
|
||||
|
||||
build:
|
||||
needs: [unit-test, type-check]
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python/llama-index-server
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install && pnpm build
|
||||
|
||||
- name: Build package
|
||||
shell: bash
|
||||
run: uv build
|
||||
|
||||
- name: Get the absolute wheel file path and save it to the output
|
||||
shell: bash
|
||||
id: get_whl_path
|
||||
run: |
|
||||
WHL_FILE=$(readlink -f dist/*.whl)
|
||||
echo "whl_file=$WHL_FILE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Test import
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
WHL_FILE: ${{ steps.get_whl_path.outputs.whl_file }}
|
||||
run: |
|
||||
uv run --with $WHL_FILE python -c "from llama_index.server import LlamaIndexServer"
|
||||
|
||||
- name: Check frontend resources is present
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
WHL_FILE: ${{ steps.get_whl_path.outputs.whl_file }}
|
||||
run: |
|
||||
uv run --with $WHL_FILE python -c "from llama_index.server.chat_ui import check_ui_resources; check_ui_resources()"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: llama-index-server
|
||||
path: dist/
|
||||
+3
-17
@@ -6,9 +6,6 @@ node_modules
|
||||
.pnpm-store
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
@@ -34,20 +31,9 @@ yarn-error.log*
|
||||
dist/
|
||||
lib/
|
||||
|
||||
# e2e
|
||||
.cache
|
||||
test-results/
|
||||
playwright-report/
|
||||
blob-report/
|
||||
playwright/.cache/
|
||||
.tsbuildinfo
|
||||
e2e/cache
|
||||
|
||||
# intellij
|
||||
**/.idea
|
||||
|
||||
# Python
|
||||
.mypy_cache/
|
||||
|
||||
# build artifacts
|
||||
create-llama-*.tgz
|
||||
# vscode
|
||||
.vscode
|
||||
!.vscode/settings.json
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
pnpm format
|
||||
pnpm lint
|
||||
uvx ruff format --check templates/
|
||||
uvx ruff check .
|
||||
uvx ruff format . --check
|
||||
|
||||
+15
-3
@@ -1,6 +1,18 @@
|
||||
apps/docs/i18n
|
||||
apps/docs/docs/api
|
||||
node_modules/
|
||||
pnpm-lock.yaml
|
||||
lib/
|
||||
dist/
|
||||
.docusaurus/
|
||||
cache/
|
||||
build/
|
||||
.next/
|
||||
out/
|
||||
packages/server/server/
|
||||
packages/server/project/
|
||||
**/playwright-report/
|
||||
**/test-results/
|
||||
|
||||
# Python
|
||||
python/
|
||||
**/*.mypy_cache/**
|
||||
**/*.venv/**
|
||||
**/*.ruff_cache/**
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Repository Overview
|
||||
|
||||
Create-llama is a monorepo containing CLI tools and server frameworks for building LlamaIndex-powered applications. The repository combines TypeScript/Node.js and Python components in a unified development environment.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Monorepo Structure
|
||||
|
||||
- **`packages/create-llama/`**: Main CLI tool for scaffolding LlamaIndex applications
|
||||
- **`packages/server/`**: TypeScript/Next.js server framework (`@llamaindex/server`)
|
||||
- **`python/llama-index-server/`**: Python/FastAPI server framework
|
||||
- **Root**: Workspace configuration and shared development tools
|
||||
|
||||
### Key Technologies
|
||||
|
||||
- **Package Manager**: pnpm with workspace configuration
|
||||
- **Build Tools**: bunchee (TypeScript), Next.js, hatchling (Python)
|
||||
- **Testing**: Playwright for e2e, pytest for Python
|
||||
- **Version Management**: changesets for TypeScript packages, manual for Python
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Root Level (Monorepo)
|
||||
|
||||
```bash
|
||||
pnpm dev # Start all packages in development mode
|
||||
pnpm build # Build all packages
|
||||
pnpm lint # ESLint across TypeScript packages
|
||||
pnpm format # Prettier formatting
|
||||
pnpm e2e # Run end-to-end tests
|
||||
```
|
||||
|
||||
### Create-llama Package
|
||||
|
||||
```bash
|
||||
cd packages/create-llama
|
||||
npm run build # Build CLI using bash script and ncc
|
||||
npm run dev # Watch mode development
|
||||
npm run e2e # Playwright tests for generated projects
|
||||
npm run clean # Clean build artifacts and template caches
|
||||
```
|
||||
|
||||
### TypeScript Server Package
|
||||
|
||||
```bash
|
||||
cd packages/server
|
||||
pnpm dev # Watch mode with bunchee
|
||||
pnpm build # Multi-step build: ESM/CJS + Next.js + static assets
|
||||
pnpm clean # Clean all build outputs
|
||||
```
|
||||
|
||||
### Python Server Package
|
||||
|
||||
```bash
|
||||
cd python/llama-index-server
|
||||
uv run generate # Index data files
|
||||
fastapi dev # Start development server with hot reload
|
||||
pytest # Run test suite
|
||||
```
|
||||
|
||||
## Template System
|
||||
|
||||
The CLI uses a sophisticated template system in `packages/create-llama/templates/`:
|
||||
|
||||
### Organization
|
||||
|
||||
- **`types/`**: Base project structures (streaming, reflex, llamaindexserver)
|
||||
- **`components/`**: Reusable components across frameworks
|
||||
- `engines/` - Chat and agent engines
|
||||
- `loaders/` - File, web, database loaders
|
||||
- `providers/` - AI model configurations
|
||||
- `vectordbs/` - Vector database integrations
|
||||
- `use-cases/` - Workflow implementations
|
||||
|
||||
### Development Workflow
|
||||
|
||||
- Templates support multiple frameworks (Next.js, Express, FastAPI)
|
||||
- Component system allows mix-and-match functionality
|
||||
- E2E tests validate generated projects work correctly
|
||||
|
||||
## Server Framework Architecture
|
||||
|
||||
### TypeScript Server (`@llamaindex/server`)
|
||||
|
||||
- **Core**: `LlamaIndexServer` class wrapping Next.js with workflow support
|
||||
- **Frontend**: React-based chat UI with shadcn/ui components
|
||||
- **API**: `/api/chat` endpoint with streaming responses
|
||||
- **Build Process**: Complex multi-step build including static assets for Python integration
|
||||
|
||||
### Python Server (`llama-index-server`)
|
||||
|
||||
- **Core**: `LlamaIndexServer` class extending FastAPI
|
||||
- **Architecture**: Workflow factory pattern for stateless request handling
|
||||
- **UI Generation**: AI-powered React component generation from Pydantic schemas
|
||||
- **Development**: Hot reloading support with dev mode
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Workflow Integration
|
||||
|
||||
Both server frameworks use factory patterns:
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
const server = new LlamaIndexServer({
|
||||
workflow: (context) => createWorkflow(context)
|
||||
});
|
||||
|
||||
// Python
|
||||
def create_workflow(chat_request: ChatRequest) -> Workflow:
|
||||
return MyWorkflow(chat_request.messages)
|
||||
```
|
||||
|
||||
### Event System
|
||||
|
||||
Structured events for UI communication:
|
||||
|
||||
- **UIEvent**: Custom components with Pydantic/Zod schemas
|
||||
- **ArtifactEvent**: Code/documents for Canvas panel
|
||||
- **SourceNodesEvent**: Document sources with metadata
|
||||
- **AgentRunEvent**: Tool usage and progress tracking
|
||||
|
||||
### File Handling
|
||||
|
||||
- Both servers auto-mount `data/` and `output/` directories
|
||||
- LlamaCloud integration for remote file access
|
||||
- Static file serving through framework-specific methods
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### E2E Testing
|
||||
|
||||
- Playwright tests in `packages/create-llama/e2e/`
|
||||
- Tests both Python and TypeScript generated projects
|
||||
- Validates CLI generation and application functionality
|
||||
|
||||
### Unit Testing
|
||||
|
||||
- Python: pytest with comprehensive API and service tests
|
||||
- TypeScript: Integrated testing through build process
|
||||
|
||||
## Build Process
|
||||
|
||||
### Create-llama CLI
|
||||
|
||||
1. TypeScript compilation with bash script
|
||||
2. ncc bundling for standalone executable
|
||||
3. Template validation and caching
|
||||
|
||||
### Server Package Build
|
||||
|
||||
1. **prebuild**: Clean directories
|
||||
2. **build**: bunchee compilation to ESM/CJS
|
||||
3. **postbuild**: Next.js preparation and static asset generation
|
||||
4. **prepare:py-static**: Python integration assets
|
||||
|
||||
### Release Process
|
||||
|
||||
```bash
|
||||
pnpm release # Build all + publish npm packages + Python release
|
||||
```
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js >=16.14.0
|
||||
- Python with uv package manager
|
||||
- pnpm for package management
|
||||
|
||||
### Common Workflow
|
||||
|
||||
1. Clone repository and run `pnpm install`
|
||||
2. For CLI development: work in `packages/create-llama/`
|
||||
3. For server development: choose TypeScript or Python package
|
||||
4. Use `pnpm dev` for concurrent development across packages
|
||||
5. Run `pnpm e2e` to validate changes with generated projects
|
||||
|
||||
## Special Considerations
|
||||
|
||||
### Template Development
|
||||
|
||||
- Changes to templates require rebuilding CLI
|
||||
- E2E tests validate template functionality across frameworks
|
||||
- Template caching system speeds up repeated builds
|
||||
|
||||
### Cross-package Dependencies
|
||||
|
||||
- Server package builds static assets for Python integration
|
||||
- Version synchronization between TypeScript and Python packages
|
||||
- Shared UI components and styling across implementations
|
||||
|
||||
### Performance
|
||||
|
||||
- CLI uses caching for template operations
|
||||
- Server frameworks support streaming responses
|
||||
- Background processing for file operations and LlamaCloud integration
|
||||
@@ -12,7 +12,7 @@ npx create-llama@latest
|
||||
|
||||
to get started, or watch this video for a demo session:
|
||||
|
||||
https://github.com/user-attachments/assets/dd3edc36-4453-4416-91c2-d24326c6c167
|
||||
<img src="https://github.com/user-attachments/assets/c4a7fe18-8e30-498a-96f8-78127dd706b9" width="100%">
|
||||
|
||||
Once your app is generated, run
|
||||
|
||||
@@ -24,14 +24,14 @@ to start the development server. You can then visit [http://localhost:3000](http
|
||||
|
||||
## What you'll get
|
||||
|
||||
- A set of pre-configured use cases to get you started, e.g. Agentic RAG, Data Analysis, Report Generation, etc.
|
||||
- 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 or interact with your agent
|
||||
- Your choice of 3 back-ends:
|
||||
- Your choice of two 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.
|
||||
- **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 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).
|
||||
- **Python FastAPI**: if you select this option, you’ll get a separate backend powered by the [llama-index Python package](https://pypi.org/project/llama-index/), which you can deploy to a service like [Render](https://render.com/) or [fly.io](https://fly.io/). The separate Next.js front-end will connect to this backend.
|
||||
- Each back-end has two endpoints:
|
||||
- One streaming chat endpoint, that allow you to send the state of your chat and receive additional responses
|
||||
- One endpoint to upload private files which can be used in your chat
|
||||
- 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.
|
||||
|
||||
Here's how it looks like:
|
||||
@@ -40,9 +40,9 @@ https://github.com/user-attachments/assets/d57af1a1-d99b-4e9c-98d9-4cbd1327eff8
|
||||
|
||||
## Using your 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`).
|
||||
Optionally, you can supply your own data; the app will index it and make use of it, e.g. to 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.
|
||||
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.
|
||||
|
||||
Before you can use your data, you need to index it. If you're using the Next.js or Express apps, run:
|
||||
|
||||
@@ -55,13 +55,9 @@ Then re-start your app. Remember you'll need to re-run `generate` if you add new
|
||||
If you're using the Python backend, you can trigger indexing of your data by calling:
|
||||
|
||||
```bash
|
||||
poetry run generate
|
||||
uv run generate
|
||||
```
|
||||
|
||||
## Want a front-end?
|
||||
|
||||
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.
|
||||
|
||||
## Customizing the AI models
|
||||
|
||||
The app will default to OpenAI's `gpt-4o-mini` LLM and `text-embedding-3-large` embedding model.
|
||||
@@ -94,50 +90,32 @@ 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? › Agentic RAG (e.g. chat with docs)
|
||||
✔ Which framework would you like to use? › NextJS
|
||||
✔ Would you like to set up observability? › No
|
||||
✔ What app do you want to build? › Agentic RAG
|
||||
✔ What language do you want to use? › Python (FastAPI)
|
||||
✔ Do you want to use LlamaCloud services? … No / Yes
|
||||
✔ Please provide your LlamaCloud API key (leave blank to skip): …
|
||||
✔ Please provide your OpenAI API key (leave blank to skip): …
|
||||
✔ 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
|
||||
✔ Would you like to build an agent using tools? If so, select the tools here, otherwise just press enter › Weather
|
||||
? 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)
|
||||
Just generate code (~1 sec)
|
||||
❯ Start in VSCode (~1 sec)
|
||||
Generate code and install dependencies (~2 min)
|
||||
```
|
||||
|
||||
### Running non-interactively
|
||||
|
||||
You can also pass command line arguments to set up a new project
|
||||
non-interactively. See `create-llama --help`:
|
||||
|
||||
```bash
|
||||
create-llama <project-directory> [options]
|
||||
|
||||
Options:
|
||||
-V, --version output the version number
|
||||
|
||||
--use-npm
|
||||
|
||||
Explicitly tell the CLI to bootstrap the app using npm
|
||||
|
||||
--use-pnpm
|
||||
|
||||
Explicitly tell the CLI to bootstrap the app using pnpm
|
||||
|
||||
--use-yarn
|
||||
|
||||
Explicitly tell the CLI to bootstrap the app using Yarn
|
||||
|
||||
```
|
||||
non-interactively. For a list of the latest options, call `create-llama --help`.
|
||||
|
||||
## LlamaIndex Documentation
|
||||
|
||||
- [TS/JS docs](https://ts.llamaindex.ai/)
|
||||
- [Python docs](https://docs.llamaindex.ai/en/stable/)
|
||||
|
||||
## LlamaIndex Server
|
||||
|
||||
The generated code is using the LlamaIndex Server, which serves LlamaIndex Workflows and Agent Workflows via an API server. See the following docs for more information:
|
||||
|
||||
- [LlamaIndex Server For TypeScript](./packages/server/README.md)
|
||||
- [LlamaIndex Server For Python](./python/llama-index-server/README.md)
|
||||
|
||||
Inspired by and adapted from [create-next-app](https://github.com/vercel/next.js/tree/canary/packages/create-next-app)
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { exec } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import util from "util";
|
||||
import { TemplateFramework, TemplateVectorDB } from "../../helpers/types";
|
||||
import { RunCreateLlamaOptions, createTestDir, runCreateLlama } from "../utils";
|
||||
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
const templateFramework: TemplateFramework = process.env.FRAMEWORK
|
||||
? (process.env.FRAMEWORK as TemplateFramework)
|
||||
: "fastapi";
|
||||
const dataSource: string = process.env.DATASOURCE
|
||||
? process.env.DATASOURCE
|
||||
: "--example-file";
|
||||
|
||||
// TODO: add support for other templates
|
||||
|
||||
if (
|
||||
dataSource === "--example-file" // XXX: this test provides its own data source - only trigger it on one data source (usually the CI matrix will trigger multiple data sources)
|
||||
) {
|
||||
// vectorDBs, tools, and data source combinations to test
|
||||
const vectorDbs: TemplateVectorDB[] = [
|
||||
"mongo",
|
||||
"pg",
|
||||
"pinecone",
|
||||
"milvus",
|
||||
"astra",
|
||||
"qdrant",
|
||||
"chroma",
|
||||
"weaviate",
|
||||
];
|
||||
|
||||
const toolOptions = [
|
||||
"wikipedia.WikipediaToolSpec",
|
||||
"google.GoogleSearchToolSpec",
|
||||
"document_generator",
|
||||
"artifact",
|
||||
];
|
||||
|
||||
const dataSources = [
|
||||
"--example-file",
|
||||
"--web-source https://www.example.com",
|
||||
"--db-source mysql+pymysql://user:pass@localhost:3306/mydb",
|
||||
];
|
||||
|
||||
const observabilityOptions = ["llamatrace", "traceloop"];
|
||||
|
||||
test.describe("Mypy check", () => {
|
||||
test.describe.configure({ retries: 0 });
|
||||
|
||||
// Test vector databases
|
||||
for (const vectorDb of vectorDbs) {
|
||||
test(`Mypy check for vectorDB: ${vectorDb}`, async () => {
|
||||
const cwd = await createTestDir();
|
||||
const { pyprojectPath } = await createAndCheckLlamaProject({
|
||||
options: {
|
||||
cwd,
|
||||
templateType: "streaming",
|
||||
templateFramework,
|
||||
dataSource: "--example-file",
|
||||
vectorDb,
|
||||
tools: "none",
|
||||
port: 3000,
|
||||
externalPort: 8000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
observability: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
|
||||
if (vectorDb !== "none") {
|
||||
if (vectorDb === "pg") {
|
||||
expect(pyprojectContent).toContain(
|
||||
"llama-index-vector-stores-postgres",
|
||||
);
|
||||
} else {
|
||||
expect(pyprojectContent).toContain(
|
||||
`llama-index-vector-stores-${vectorDb}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test tools
|
||||
for (const tool of toolOptions) {
|
||||
test(`Mypy check for tool: ${tool}`, async () => {
|
||||
const cwd = await createTestDir();
|
||||
const { pyprojectPath } = await createAndCheckLlamaProject({
|
||||
options: {
|
||||
cwd,
|
||||
templateType: "streaming",
|
||||
templateFramework,
|
||||
dataSource: "--example-file",
|
||||
vectorDb: "none",
|
||||
tools: tool,
|
||||
port: 3000,
|
||||
externalPort: 8000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
observability: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
|
||||
if (tool === "wikipedia.WikipediaToolSpec") {
|
||||
expect(pyprojectContent).toContain("wikipedia");
|
||||
}
|
||||
if (tool === "google.GoogleSearchToolSpec") {
|
||||
expect(pyprojectContent).toContain("google");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test data sources
|
||||
for (const dataSource of dataSources) {
|
||||
const dataSourceType = dataSource.split(" ")[0];
|
||||
test(`Mypy check for data source: ${dataSourceType}`, async () => {
|
||||
const cwd = await createTestDir();
|
||||
const { pyprojectPath } = await createAndCheckLlamaProject({
|
||||
options: {
|
||||
cwd,
|
||||
templateType: "streaming",
|
||||
templateFramework,
|
||||
dataSource,
|
||||
vectorDb: "none",
|
||||
tools: "none",
|
||||
port: 3000,
|
||||
externalPort: 8000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
observability: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
|
||||
if (dataSource.includes("--web-source")) {
|
||||
expect(pyprojectContent).toContain("llama-index-readers-web");
|
||||
}
|
||||
if (dataSource.includes("--db-source")) {
|
||||
expect(pyprojectContent).toContain("llama-index-readers-database");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test observability options
|
||||
for (const observability of observabilityOptions) {
|
||||
test(`Mypy check for observability: ${observability}`, async () => {
|
||||
const cwd = await createTestDir();
|
||||
|
||||
const { pyprojectPath } = await createAndCheckLlamaProject({
|
||||
options: {
|
||||
cwd,
|
||||
templateType: "streaming",
|
||||
templateFramework,
|
||||
dataSource: "--example-file",
|
||||
vectorDb: "none",
|
||||
tools: "none",
|
||||
port: 3000,
|
||||
externalPort: 8000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
observability,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function createAndCheckLlamaProject({
|
||||
options,
|
||||
}: {
|
||||
options: RunCreateLlamaOptions;
|
||||
}): Promise<{ pyprojectPath: string; projectPath: string }> {
|
||||
const result = await runCreateLlama(options);
|
||||
const name = result.projectName;
|
||||
const projectPath = path.join(options.cwd, name);
|
||||
|
||||
// Check if the app folder exists
|
||||
expect(fs.existsSync(projectPath)).toBeTruthy();
|
||||
|
||||
// Check if pyproject.toml exists
|
||||
const pyprojectPath = path.join(projectPath, "pyproject.toml");
|
||||
expect(fs.existsSync(pyprojectPath)).toBeTruthy();
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
POETRY_VIRTUALENVS_IN_PROJECT: "true",
|
||||
};
|
||||
|
||||
// Run poetry install
|
||||
try {
|
||||
const { stdout: installStdout, stderr: installStderr } = await execAsync(
|
||||
"poetry install",
|
||||
{ cwd: projectPath, env },
|
||||
);
|
||||
console.log("poetry install stdout:", installStdout);
|
||||
console.error("poetry install stderr:", installStderr);
|
||||
} catch (error) {
|
||||
console.error("Error running poetry install:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Run poetry run mypy
|
||||
try {
|
||||
const { stdout: mypyStdout, stderr: mypyStderr } = await execAsync(
|
||||
"poetry run mypy .",
|
||||
{ cwd: projectPath, env },
|
||||
);
|
||||
console.log("poetry run mypy stdout:", mypyStdout);
|
||||
console.error("poetry run mypy stderr:", mypyStderr);
|
||||
} catch (error) {
|
||||
console.error("Error running mypy:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// If we reach this point without throwing an error, the test passes
|
||||
expect(true).toBeTruthy();
|
||||
|
||||
return { pyprojectPath, projectPath };
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { ChildProcess } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { TemplateFramework } from "../../helpers";
|
||||
import { createTestDir, runCreateLlama } from "../utils";
|
||||
|
||||
const templateFramework: TemplateFramework = process.env.FRAMEWORK
|
||||
? (process.env.FRAMEWORK as TemplateFramework)
|
||||
: "fastapi";
|
||||
const dataSource: string = process.env.DATASOURCE
|
||||
? process.env.DATASOURCE
|
||||
: "--example-file";
|
||||
|
||||
// The extractor template currently only works with FastAPI and files (and not on Windows)
|
||||
if (
|
||||
process.platform !== "win32" &&
|
||||
templateFramework === "fastapi" &&
|
||||
dataSource === "--example-file"
|
||||
) {
|
||||
test.describe("Test extractor template", async () => {
|
||||
let frontendPort: number;
|
||||
let backendPort: number;
|
||||
let name: string;
|
||||
let appProcess: ChildProcess;
|
||||
let cwd: string;
|
||||
|
||||
// Create extractor app
|
||||
test.beforeAll(async () => {
|
||||
cwd = await createTestDir();
|
||||
frontendPort = Math.floor(Math.random() * 10000) + 10000;
|
||||
backendPort = frontendPort + 1;
|
||||
const result = await runCreateLlama({
|
||||
cwd,
|
||||
templateType: "extractor",
|
||||
templateFramework: "fastapi",
|
||||
dataSource: "--example-file",
|
||||
vectorDb: "none",
|
||||
port: frontendPort,
|
||||
externalPort: backendPort,
|
||||
postInstallAction: "runApp",
|
||||
});
|
||||
name = result.projectName;
|
||||
appProcess = result.appProcess;
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
appProcess.kill();
|
||||
});
|
||||
|
||||
test("App folder should exist", async () => {
|
||||
const dirExists = fs.existsSync(path.join(cwd, name));
|
||||
expect(dirExists).toBeTruthy();
|
||||
});
|
||||
test("Frontend should have a title", async ({ page }) => {
|
||||
await page.goto(`http://localhost:${frontendPort}`);
|
||||
await expect(page.getByText("Built by LlamaIndex")).toBeVisible({
|
||||
timeout: 2000 * 60,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { ChildProcess } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import type {
|
||||
TemplateFramework,
|
||||
TemplatePostInstallAction,
|
||||
TemplateUI,
|
||||
} from "../../helpers";
|
||||
import { createTestDir, runCreateLlama, type AppType } from "../utils";
|
||||
|
||||
const templateFramework: TemplateFramework = process.env.FRAMEWORK
|
||||
? (process.env.FRAMEWORK as TemplateFramework)
|
||||
: "fastapi";
|
||||
const dataSource: string = "--example-file";
|
||||
const templateUI: TemplateUI = "shadcn";
|
||||
const templatePostInstallAction: TemplatePostInstallAction = "runApp";
|
||||
const appType: AppType = templateFramework === "nextjs" ? "" : "--frontend";
|
||||
const userMessage = "Write a blog post about physical standards for letters";
|
||||
|
||||
test.describe(`Test multiagent template ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
|
||||
test.skip(
|
||||
process.platform !== "linux" || process.env.DATASOURCE === "--no-files",
|
||||
"The multiagent template currently only works with files. We also only run on Linux to speed up tests.",
|
||||
);
|
||||
let port: number;
|
||||
let externalPort: number;
|
||||
let cwd: string;
|
||||
let name: string;
|
||||
let appProcess: ChildProcess;
|
||||
// Only test without using vector db for now
|
||||
const vectorDb = "none";
|
||||
|
||||
test.beforeAll(async () => {
|
||||
port = Math.floor(Math.random() * 10000) + 10000;
|
||||
externalPort = port + 1;
|
||||
cwd = await createTestDir();
|
||||
const result = await runCreateLlama({
|
||||
cwd,
|
||||
templateType: "multiagent",
|
||||
templateFramework,
|
||||
dataSource,
|
||||
vectorDb,
|
||||
port,
|
||||
externalPort,
|
||||
postInstallAction: templatePostInstallAction,
|
||||
templateUI,
|
||||
appType,
|
||||
});
|
||||
name = result.projectName;
|
||||
appProcess = result.appProcess;
|
||||
});
|
||||
|
||||
test("App folder should exist", async () => {
|
||||
const dirExists = fs.existsSync(path.join(cwd, name));
|
||||
expect(dirExists).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Frontend should have a title", async ({ page }) => {
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Frontend should be able to submit a message and receive the start of a streamed response", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await page.fill("form textarea", userMessage);
|
||||
|
||||
const responsePromise = page.waitForResponse((res) =>
|
||||
res.url().includes("/api/chat"),
|
||||
);
|
||||
|
||||
await page.click("form button[type=submit]");
|
||||
|
||||
const response = await responsePromise;
|
||||
expect(response.ok()).toBeTruthy();
|
||||
});
|
||||
|
||||
// clean processes
|
||||
test.afterAll(async () => {
|
||||
appProcess?.kill();
|
||||
});
|
||||
});
|
||||
@@ -1,106 +0,0 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { exec } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import util from "util";
|
||||
import { TemplateFramework, TemplateVectorDB } from "../../helpers/types";
|
||||
import { createTestDir, runCreateLlama } from "../utils";
|
||||
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
const templateFramework: TemplateFramework = process.env.FRAMEWORK
|
||||
? (process.env.FRAMEWORK as TemplateFramework)
|
||||
: "nextjs";
|
||||
const dataSource: string = process.env.DATASOURCE
|
||||
? process.env.DATASOURCE
|
||||
: "--example-file";
|
||||
|
||||
// vectorDBs combinations to test
|
||||
const vectorDbs: TemplateVectorDB[] = [
|
||||
"mongo",
|
||||
"pg",
|
||||
"qdrant",
|
||||
"pinecone",
|
||||
"milvus",
|
||||
"astra",
|
||||
"chroma",
|
||||
"llamacloud",
|
||||
"weaviate",
|
||||
];
|
||||
|
||||
test.describe("Test resolve TS dependencies", () => {
|
||||
// Test vector DBs without LlamaParse
|
||||
for (const vectorDb of vectorDbs) {
|
||||
const optionDescription = `vectorDb: ${vectorDb}, dataSource: ${dataSource}`;
|
||||
|
||||
test(`Vector DB test - ${optionDescription}`, async () => {
|
||||
await runTest(vectorDb, false);
|
||||
});
|
||||
}
|
||||
|
||||
// Test LlamaParse with vectorDB 'none'
|
||||
test(`LlamaParse test - vectorDb: none, dataSource: ${dataSource}, llamaParse: true`, async () => {
|
||||
await runTest("none", true);
|
||||
});
|
||||
|
||||
async function runTest(
|
||||
vectorDb: TemplateVectorDB | "none",
|
||||
useLlamaParse: boolean,
|
||||
) {
|
||||
const cwd = await createTestDir();
|
||||
|
||||
const result = await runCreateLlama({
|
||||
cwd: cwd,
|
||||
templateType: "streaming",
|
||||
templateFramework: templateFramework,
|
||||
dataSource: dataSource,
|
||||
vectorDb: vectorDb,
|
||||
port: 3000,
|
||||
externalPort: 8000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: templateFramework === "nextjs" ? "" : "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
tools: undefined,
|
||||
useLlamaParse: useLlamaParse,
|
||||
});
|
||||
const name = result.projectName;
|
||||
|
||||
// Check if the app folder exists
|
||||
const appDir = path.join(cwd, name);
|
||||
const dirExists = fs.existsSync(appDir);
|
||||
expect(dirExists).toBeTruthy();
|
||||
|
||||
// Install dependencies using pnpm
|
||||
try {
|
||||
const { stderr: installStderr } = await execAsync(
|
||||
"pnpm install --prefer-offline",
|
||||
{
|
||||
cwd: appDir,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error installing dependencies:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Run tsc type check and capture the output
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(
|
||||
"pnpm exec tsc -b --diagnostics",
|
||||
{
|
||||
cwd: appDir,
|
||||
},
|
||||
);
|
||||
// Check if there's any error output
|
||||
expect(stderr).toBeFalsy();
|
||||
|
||||
// Log the stdout for debugging purposes
|
||||
console.log("TypeScript type-check output:", stdout);
|
||||
} catch (error) {
|
||||
console.error("Error running tsc:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import eslint from "@eslint/js";
|
||||
import eslintConfigPrettier from "eslint-config-prettier";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: "module",
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/create-llama/**"],
|
||||
rules: {
|
||||
"max-params": ["error", 4],
|
||||
"prefer-const": "error",
|
||||
"no-empty": "off",
|
||||
"no-extra-boolean-cast": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-empty-object-type": "off",
|
||||
"@typescript-eslint/no-wrapper-object-types": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/server/**"],
|
||||
rules: {
|
||||
"no-irregular-whitespace": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": [
|
||||
"error",
|
||||
{
|
||||
ignoreRestArgs: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"python/**",
|
||||
"**/*.mypy_cache/**",
|
||||
"**/*.venv/**",
|
||||
"**/*.ruff_cache/**",
|
||||
"**/dist/**",
|
||||
"**/e2e/cache/**",
|
||||
"**/lib/*",
|
||||
"**/.next/**",
|
||||
"**/out/**",
|
||||
"**/node_modules/**",
|
||||
"**/build/**",
|
||||
"packages/server/server/**",
|
||||
"packages/server/project/**",
|
||||
"packages/server/bin/**",
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -1,99 +0,0 @@
|
||||
import { ChildProcess, SpawnOptions, spawn } from "child_process";
|
||||
import path from "path";
|
||||
import { TemplateFramework } from "./types";
|
||||
|
||||
const createProcess = (
|
||||
command: string,
|
||||
args: string[],
|
||||
options: SpawnOptions,
|
||||
) => {
|
||||
return spawn(command, args, {
|
||||
...options,
|
||||
shell: true,
|
||||
})
|
||||
.on("exit", function (code) {
|
||||
if (code !== 0) {
|
||||
console.log(`Child process exited with code=${code}`);
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.on("error", function (err) {
|
||||
console.log("Error when running chill process: ", err);
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
export function runReflexApp(
|
||||
appPath: string,
|
||||
frontendPort?: number,
|
||||
backendPort?: number,
|
||||
) {
|
||||
const commandArgs = ["run", "reflex", "run"];
|
||||
if (frontendPort) {
|
||||
commandArgs.push("--frontend-port", frontendPort.toString());
|
||||
}
|
||||
if (backendPort) {
|
||||
commandArgs.push("--backend-port", backendPort.toString());
|
||||
}
|
||||
return createProcess("poetry", commandArgs, {
|
||||
stdio: "inherit",
|
||||
cwd: appPath,
|
||||
});
|
||||
}
|
||||
|
||||
export function runFastAPIApp(appPath: string, port: number) {
|
||||
const commandArgs = ["run", "uvicorn", "main:app", "--port=" + port];
|
||||
|
||||
return createProcess("poetry", commandArgs, {
|
||||
stdio: "inherit",
|
||||
cwd: appPath,
|
||||
});
|
||||
}
|
||||
|
||||
export function runTSApp(appPath: string, port: number) {
|
||||
return createProcess("npm", ["run", "dev"], {
|
||||
stdio: "inherit",
|
||||
cwd: appPath,
|
||||
env: { ...process.env, PORT: `${port}` },
|
||||
});
|
||||
}
|
||||
|
||||
export async function runApp(
|
||||
appPath: string,
|
||||
template: string,
|
||||
frontend: boolean,
|
||||
framework: TemplateFramework,
|
||||
port?: number,
|
||||
externalPort?: number,
|
||||
): Promise<any> {
|
||||
const processes: ChildProcess[] = [];
|
||||
|
||||
// Callback to kill all sub processes if the main process is killed
|
||||
process.on("exit", () => {
|
||||
console.log("Killing app processes...");
|
||||
processes.forEach((p) => p.kill());
|
||||
});
|
||||
|
||||
// Default sub app paths
|
||||
const backendPath = path.join(appPath, "backend");
|
||||
const frontendPath = path.join(appPath, "frontend");
|
||||
|
||||
if (template === "extractor") {
|
||||
processes.push(runReflexApp(appPath, port, externalPort));
|
||||
}
|
||||
if (template === "streaming" || template === "multiagent") {
|
||||
if (framework === "fastapi" || framework === "express") {
|
||||
const backendRunner = framework === "fastapi" ? runFastAPIApp : runTSApp;
|
||||
if (frontend) {
|
||||
processes.push(backendRunner(backendPath, externalPort || 8000));
|
||||
processes.push(runTSApp(frontendPath, port || 3000));
|
||||
} else {
|
||||
processes.push(backendRunner(appPath, externalPort || 8000));
|
||||
}
|
||||
} else if (framework === "nextjs") {
|
||||
processes.push(runTSApp(appPath, port || 3000));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(processes);
|
||||
}
|
||||
+35
-63
@@ -1,83 +1,55 @@
|
||||
{
|
||||
"name": "create-llama",
|
||||
"version": "0.2.19",
|
||||
"description": "Create LlamaIndex-powered apps with one command",
|
||||
"name": "create-llama-monorepo",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Monorepo for create-llama",
|
||||
"keywords": [
|
||||
"rag",
|
||||
"llamaindex",
|
||||
"next.js"
|
||||
"llamaindex"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/run-llama/create-llama",
|
||||
"directory": "packages/create-llama"
|
||||
"url": "https://github.com/run-llama/create-llama"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"create-llama": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"python/*"
|
||||
],
|
||||
"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",
|
||||
"e2e:python": "playwright test e2e/shared e2e/python",
|
||||
"e2e:typescript": "playwright test e2e/shared e2e/typescript",
|
||||
"dev": "pnpm -r dev",
|
||||
"build": "pnpm -r build",
|
||||
"e2e": "pnpm -r e2e",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --ignore-unknown --cache --check .",
|
||||
"format:write": "prettier --ignore-unknown --write .",
|
||||
"lint": "eslint . --ignore-pattern dist --ignore-pattern e2e/cache",
|
||||
"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",
|
||||
"release-snapshot": "pnpm run build && changeset publish --tag snapshot"
|
||||
},
|
||||
"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",
|
||||
"async-retry": "1.3.1",
|
||||
"async-sema": "3.0.1",
|
||||
"ci-info": "github:watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
|
||||
"commander": "12.1.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.4.2",
|
||||
"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",
|
||||
"yaml": "2.4.1"
|
||||
"new-snapshot": "pnpm -r build && changeset version --snapshot",
|
||||
"new-version-python": "pnpm --filter @create-llama/llama-index-server new-version",
|
||||
"new-version": "pnpm -r build && changeset version && pnpm new-version-python",
|
||||
"release-python": "pnpm --filter @create-llama/llama-index-server release",
|
||||
"release": "pnpm -r build && changeset publish && pnpm release-python",
|
||||
"release-snapshot": "pnpm -r build && changeset publish --tag snapshot"
|
||||
},
|
||||
"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",
|
||||
"bunchee": "6.4.0",
|
||||
"husky": "^9.0.10",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"rimraf": "^5.0.5",
|
||||
"typescript": "^5.3.3",
|
||||
"wait-port": "^1.1.0"
|
||||
"lint-staged": "^15.2.11",
|
||||
"typescript-eslint": "^8.18.0",
|
||||
"globals": "^15.12.0",
|
||||
"eslint": "9.22.0",
|
||||
"@eslint/js": "^9.25.0",
|
||||
"eslint-config-next": "^15.1.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-react": "7.37.2",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"typescript": "^5.7.3",
|
||||
"@types/node": "^22.9.0",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19"
|
||||
},
|
||||
"packageManager": "pnpm@9.0.5",
|
||||
"engines": {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnpm-store
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
.coverage
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# build
|
||||
dist/
|
||||
lib/
|
||||
|
||||
# e2e
|
||||
.cache
|
||||
test-results/
|
||||
playwright-report/
|
||||
blob-report/
|
||||
playwright/.cache/
|
||||
.tsbuildinfo
|
||||
e2e/cache
|
||||
|
||||
# intellij
|
||||
**/.idea
|
||||
|
||||
# Python
|
||||
.mypy_cache/
|
||||
venv/
|
||||
.venv/
|
||||
dist/
|
||||
.__pycache__
|
||||
__pycache__
|
||||
.python-version
|
||||
.ui
|
||||
|
||||
# build artifacts
|
||||
create-llama-*.tgz
|
||||
|
||||
# copied from root
|
||||
README.md
|
||||
LICENSE.md
|
||||
@@ -1,5 +1,357 @@
|
||||
# create-llama
|
||||
|
||||
## 0.5.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3ff0a18: fix: default header padding
|
||||
|
||||
## 0.5.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5fe9e17: support eject to fully customize next folder
|
||||
- b8a1ff6: Support citation for agentic template (Python)
|
||||
|
||||
## 0.5.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8d59ef0: Add layout_dir config to the generated python code
|
||||
|
||||
## 0.5.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- eee3230: feat: support custom layout
|
||||
|
||||
## 0.5.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6f75d4a: fix: unsupported language in code gen workflow
|
||||
- d0618fa: Fix LlamaCloud generate script issue
|
||||
|
||||
## 0.5.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 527075c: Enable dev mode that allows updating code directly in the UI
|
||||
|
||||
## 0.5.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1df8cfb: Split artifacts use case to document generator and code generator
|
||||
- 1b5a519: chore: improve dev experience with nodemon
|
||||
- b3eb0ba: Fix typing check issue
|
||||
- 556f33c: fix chromadb dependency issue
|
||||
- 2451539: fix: remove dead generated ai code
|
||||
- 7a70390: Deprecate pro mode
|
||||
|
||||
## 0.5.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f4ca602: Add artifact use case for Typescript template
|
||||
- f4ca602: Update typescript use cases to use the new workflow engine
|
||||
|
||||
## 0.5.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 241d82a: Add artifacts use case (python)
|
||||
|
||||
## 0.5.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3960618: chore: create-llama monorepo
|
||||
- 8fe5fc2: chore: add llamaindex server package
|
||||
|
||||
## 0.5.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0a2e12a: Use uv as the default package manager
|
||||
|
||||
## 0.5.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4bc53ac: Bump new chat ui and update deep research component
|
||||
- 4bc53ac: Support generate UI for deep research use case (Typescript)
|
||||
|
||||
## 0.5.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 765181a: chore: test typescript e2e with node 20 and 22
|
||||
|
||||
## 0.5.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5988657: chore: bump llmaindex
|
||||
|
||||
## 0.5.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d363ced: Bump llamaindex server packages
|
||||
|
||||
## 0.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ee85320: The default custom deep research component does not work.
|
||||
|
||||
## 0.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7c3b279: Support code generation of event components using an LLM (Python)
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 76ec360: Update templates to use new chat ui config
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c9f8f8d: Use custom component for deep research use case
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 08b3e07: Simplify the local index code.
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 54c9e2f: Simplified generated code using LlamaIndexServer
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0e4ecfa: fix: add trycatch for generating error
|
||||
- ee69ce7: bump: chat-ui and tailwind v4
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 61204a1: chore: bump LITS 0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9e723c3: Standardize the code of the workflow use case (Python)
|
||||
- d5da55b: feat: add components.json to use CLI
|
||||
- c1552eb: chore: move wikipedia tool to create-llama
|
||||
|
||||
## 0.3.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4e06714: Fix the error: Unable to view file sources due to CORS.
|
||||
|
||||
## 0.3.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b4e41aa: Add deep research over own documents use case (Python)
|
||||
|
||||
## 0.3.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f73d46b: Fix missing copy of the multiagent code
|
||||
|
||||
## 0.3.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5450096: bump: react 19 stable
|
||||
|
||||
## 0.3.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a84743c: Change --agents paramameter to --use-case
|
||||
- a84743c: Add LlamaCloud support for Reflex templates
|
||||
- a7a6592: Fix the npm issue on the full-stack Python template
|
||||
- fc5e56e: bump: code interpreter v1
|
||||
|
||||
## 0.3.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9077cae: Add contract review use case (Python)
|
||||
|
||||
## 0.3.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 25667d4: Make OpenAPI spec usable by custom GPTs
|
||||
|
||||
## 0.3.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 95227a7: Add query endpoint
|
||||
|
||||
## 0.3.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 27d2499: Bump the LlamaCloud library and fix breaking changes (Python).
|
||||
|
||||
## 0.3.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f9a057d: Add support multimodal indexes (e.g. from LlamaCloud)
|
||||
- aedd73d: bump: chat-ui
|
||||
|
||||
## 0.3.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fe90a7e: chore: bump ai v4
|
||||
- 02b2473: Show streaming errors in Python, optimize system prompts for tool usage and set the weather tool as default for the Agentic RAG use case
|
||||
- 63e961e: Use auto_routed retriever mode for LlamaCloudIndex
|
||||
|
||||
## 0.3.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 28c8808: Add fly.io deployment
|
||||
- 0a7dfcf: Generate NEXT_PUBLIC_CHAT_API for NextJS backend to specify alternative backend
|
||||
|
||||
## 0.3.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8b371d8: Set pydantic version to <2.10 to avoid incompatibility with llama-index.
|
||||
- 30fe269: Deactive duckduckgo tool for TS
|
||||
- 30fe269: Replace DuckDuckGo by Wikipedia tool for agentic template
|
||||
|
||||
## 0.3.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fc5b266: Improve DX for Python template (use one deployment instead of two)
|
||||
- f8f97d2: Add support for python 3.13
|
||||
|
||||
## 0.3.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 00f0b3a: fix: dont include user message in chat history
|
||||
- 4663dec: chore: bump react19 rc
|
||||
- 44b34fb: chore: update eslint 9, nextjs 15, react 19
|
||||
- 6925676: feat: use latest chat UI
|
||||
|
||||
## 0.3.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 282eaa0: Ensure that the index and document store are created when uploading a file with no available index.
|
||||
|
||||
## 0.3.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6edea6a: Optimize generated workflow code for Python
|
||||
- 8431b78: Optimize Typescript multi-agent code
|
||||
- 8431b78: Add form filling use case (Typescript)
|
||||
|
||||
## 0.3.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2b8aaa8: Add support for local models via Hugging Face
|
||||
- b9570b2: Fix: use generic LLMAgent instead of OpenAIAgent (adds support for Gemini and Anthropic for Agentic RAG)
|
||||
- 1fe21f8: Fix the highlight.js issue with the Next.js static build
|
||||
- 00009ae: feat: import pdf css
|
||||
|
||||
## 0.3.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9172fed: feat: bump LITS 0.8.2
|
||||
- 78ccde7: feat: use llamaindex chat-ui for nextjs frontend
|
||||
|
||||
## 0.3.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ed59927: Add form filling use case (Python)
|
||||
|
||||
## 0.3.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4a83469: Add multi-agent financial report for Typescript (and update LITS to 0.7.10)
|
||||
|
||||
## 0.3.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fa80378: DocumentInfo working with relative URLs
|
||||
|
||||
## 0.3.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0182368: Fix the streaming issue to prevent the UI from hanging.
|
||||
|
||||
## 0.3.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2209409: Add financial report as the default use case in the multi-agent template (Python).
|
||||
|
||||
## 0.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 384a136: Fix import error if the artifact tool is selected
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 99b8247: Simplify and unify handling file uploads
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6d1b6b9: Update README.md for pro mode
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f3577c5: Fix event streaming is blocked
|
||||
- f3577c5: Add upload file to sandbox (artifact and code interpreter)
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7562cb4: Simplified default questions and added pro mode
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0a69fe0: fix: missing params when init Astra vectorstore
|
||||
- 98a82b0: docs: chroma env variables
|
||||
|
||||
## 0.2.19
|
||||
|
||||
### Patch Changes
|
||||
@@ -0,0 +1,108 @@
|
||||
# create-llama Package
|
||||
|
||||
## Overview
|
||||
|
||||
The `create-llama` package is a CLI tool for creating LlamaIndex-powered applications with one command. It's designed as a project generator that scaffolds various types of RAG (Retrieval-Augmented Generation) applications using different frameworks, databases, and AI model providers.
|
||||
|
||||
## Package Structure
|
||||
|
||||
### Core Files
|
||||
|
||||
- **`index.ts`**: Main CLI entry point using Commander.js for argument parsing
|
||||
- **`create-app.ts`**: Core application creation logic and orchestration
|
||||
- **`package.json`**: Package configuration with binary entry point at `./dist/index.js`
|
||||
|
||||
### Key Directories
|
||||
|
||||
- **`helpers/`**: Utility functions for package management, file operations, and configuration
|
||||
- **`questions/`**: Interactive prompts for user configuration
|
||||
- **`templates/`**: Project templates for different frameworks and use cases
|
||||
- **`e2e/`**: End-to-end tests using Playwright
|
||||
|
||||
## Core Functionality
|
||||
|
||||
### CLI Interface
|
||||
|
||||
The tool accepts numerous command-line options including:
|
||||
|
||||
- Framework selection (`--framework`: nextjs, express, fastapi)
|
||||
- Template type (`--template`: streaming, multiagent, reflex, llamaindexserver)
|
||||
- Model providers (OpenAI, Anthropic, Groq, Ollama, etc.)
|
||||
- Vector databases (none, mongo, pg, pinecone, milvus, etc.)
|
||||
- Data sources (files, web URLs, databases)
|
||||
- Tools and observability options
|
||||
|
||||
### Application Generation Flow
|
||||
|
||||
1. **Project validation**: Checks project name validity and directory permissions
|
||||
2. **Interactive questioning**: Prompts user for configuration if not provided via CLI
|
||||
3. **Template installation**: Copies and configures appropriate templates
|
||||
4. **Environment setup**: Creates `.env` files with API keys and configuration
|
||||
5. **Dependencies**: Installs packages using detected/specified package manager
|
||||
6. **Post-install actions**: Can run the app, open VSCode, or install dependencies
|
||||
|
||||
### Template System
|
||||
|
||||
Templates are organized by:
|
||||
|
||||
- **Framework**: NextJS (frontend), Express (Node backend), FastAPI (Python backend)
|
||||
- **Type**: Streaming chat, multiagent workflows, Reflex UI, LlamaIndex server
|
||||
- **Components**: Engines, loaders, providers, UI components, observability
|
||||
|
||||
### Helper Functions
|
||||
|
||||
Key helper modules include:
|
||||
|
||||
- **Installation**: Package manager detection and dependency installation
|
||||
- **Data sources**: File copying, web scraping, database connection setup
|
||||
- **Providers**: Model provider configuration (OpenAI, Anthropic, etc.)
|
||||
- **Tools**: Integration with external tools (Wikipedia, weather, code generation)
|
||||
- **Environment**: `.env` file generation with API keys and settings
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Build & Development
|
||||
|
||||
- `npm run build`: Build the CLI using bash script
|
||||
- `npm run dev`: Watch mode development build
|
||||
- `npm run clean`: Clean build artifacts and temporary files
|
||||
|
||||
### Testing
|
||||
|
||||
- `npm run e2e`: Run all end-to-end tests
|
||||
- `npm run e2e:python`: Test Python-specific templates
|
||||
- `npm run e2e:typescript`: Test TypeScript-specific templates
|
||||
|
||||
### Package Management
|
||||
|
||||
- `npm run pack-install`: Create and install local package for testing
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
### Model Configuration
|
||||
|
||||
The tool supports multiple AI providers with a unified `ModelConfig` interface that includes:
|
||||
|
||||
- Provider selection and API key management
|
||||
- Model and embedding model specification
|
||||
- Dimension configuration for embeddings
|
||||
|
||||
### Data Source Handling
|
||||
|
||||
Flexible data source configuration supporting:
|
||||
|
||||
- Local files and directories
|
||||
- Web URLs with configurable crawling depth
|
||||
- Database connections with custom queries
|
||||
- Automatic file downloading and copying
|
||||
|
||||
### Template Flexibility
|
||||
|
||||
Templates use a component-based system allowing mix-and-match of:
|
||||
|
||||
- Different frameworks (NextJS, Express, FastAPI)
|
||||
- Various vector databases
|
||||
- Multiple observability tools
|
||||
- Configurable tools and integrations
|
||||
|
||||
This package serves as the foundation for rapidly prototyping and deploying LlamaIndex applications across different technology stacks and use cases.
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import path from "path";
|
||||
import { green, yellow } from "picocolors";
|
||||
import { tryGitInit } from "./helpers/git";
|
||||
@@ -7,17 +6,16 @@ import { getOnline } from "./helpers/is-online";
|
||||
import { isWriteable } from "./helpers/is-writeable";
|
||||
import { makeDir } from "./helpers/make-dir";
|
||||
|
||||
import fs from "fs";
|
||||
import terminalLink from "terminal-link";
|
||||
import type { InstallTemplateArgs, TemplateObservability } from "./helpers";
|
||||
import { installTemplate } from "./helpers";
|
||||
import { writeDevcontainer } from "./helpers/devcontainer";
|
||||
import { templatesDir } from "./helpers/dir";
|
||||
import { toolsRequireConfig } from "./helpers/tools";
|
||||
import { configVSCode } from "./helpers/vscode";
|
||||
|
||||
export type InstallAppArgs = Omit<
|
||||
InstallTemplateArgs,
|
||||
"appName" | "root" | "isOnline" | "customApiPath"
|
||||
"appName" | "root" | "isOnline" | "port"
|
||||
> & {
|
||||
appPath: string;
|
||||
frontend: boolean;
|
||||
@@ -35,12 +33,12 @@ export async function createApp({
|
||||
communityProjectConfig,
|
||||
llamapack,
|
||||
vectorDb,
|
||||
externalPort,
|
||||
postInstallAction,
|
||||
dataSources,
|
||||
tools,
|
||||
useLlamaParse,
|
||||
observability,
|
||||
useCase,
|
||||
}: InstallAppArgs): Promise<void> {
|
||||
const root = path.resolve(appPath);
|
||||
|
||||
@@ -80,39 +78,30 @@ export async function createApp({
|
||||
communityProjectConfig,
|
||||
llamapack,
|
||||
vectorDb,
|
||||
externalPort,
|
||||
postInstallAction,
|
||||
dataSources,
|
||||
tools,
|
||||
useLlamaParse,
|
||||
observability,
|
||||
useCase,
|
||||
};
|
||||
|
||||
if (frontend) {
|
||||
// install backend
|
||||
const backendRoot = path.join(root, "backend");
|
||||
await makeDir(backendRoot);
|
||||
await installTemplate({ ...args, root: backendRoot, backend: true });
|
||||
// Install backend
|
||||
await installTemplate({ ...args, backend: true });
|
||||
|
||||
if (frontend && framework === "fastapi" && template !== "llamaindexserver") {
|
||||
// install frontend
|
||||
const frontendRoot = path.join(root, "frontend");
|
||||
const frontendRoot = path.join(root, ".frontend");
|
||||
await makeDir(frontendRoot);
|
||||
await installTemplate({
|
||||
...args,
|
||||
root: frontendRoot,
|
||||
framework: "nextjs",
|
||||
customApiPath: `http://localhost:${externalPort ?? 8000}/api/chat`,
|
||||
backend: false,
|
||||
});
|
||||
// copy readme for fullstack
|
||||
await fs.promises.copyFile(
|
||||
path.join(templatesDir, "README-fullstack.md"),
|
||||
path.join(root, "README.md"),
|
||||
);
|
||||
} else {
|
||||
await installTemplate({ ...args, backend: true });
|
||||
}
|
||||
|
||||
await writeDevcontainer(root, templatesDir, framework, frontend);
|
||||
await configVSCode(root, templatesDir, framework);
|
||||
|
||||
process.chdir(root);
|
||||
if (tryGitInit(root)) {
|
||||
@@ -120,7 +109,7 @@ export async function createApp({
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (toolsRequireConfig(tools)) {
|
||||
if (toolsRequireConfig(tools) && template !== "llamaindexserver") {
|
||||
const configFile =
|
||||
framework === "fastapi" ? "config/tools.yaml" : "config/tools.json";
|
||||
console.log(
|
||||
@@ -0,0 +1,285 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { exec } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import util from "util";
|
||||
import { TemplateFramework, TemplateType, TemplateUseCase, TemplateVectorDB } from "../../helpers/types";
|
||||
import { RunCreateLlamaOptions, createTestDir, runCreateLlama } from "../utils";
|
||||
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
const templateFramework: TemplateFramework = process.env.FRAMEWORK
|
||||
? (process.env.FRAMEWORK as TemplateFramework)
|
||||
: "fastapi";
|
||||
const templateType: TemplateType = process.env.TEMPLATE_TYPE
|
||||
? (process.env.TEMPLATE_TYPE as TemplateType)
|
||||
: "llamaindexserver";
|
||||
const useCases: TemplateUseCase[] = [
|
||||
"agentic_rag",
|
||||
"deep_research",
|
||||
"financial_report",
|
||||
"code_generator",
|
||||
"document_generator",
|
||||
];
|
||||
const dataSource: string = process.env.DATASOURCE
|
||||
? process.env.DATASOURCE
|
||||
: "--example-file";
|
||||
|
||||
test.describe("Mypy check", () => {
|
||||
test.describe.configure({ retries: 0 });
|
||||
|
||||
// Test for streaming template
|
||||
test.describe("StreamingTemplate", () => {
|
||||
test.skip(templateType !== "streaming", `skipping streaming test for ${templateType}`);
|
||||
if (
|
||||
dataSource === "--example-file" // XXX: this test provides its own data source - only trigger it on one data source (usually the CI matrix will trigger multiple data sources)
|
||||
) {
|
||||
// vectorDBs, tools, and data source combinations to test
|
||||
const vectorDbs: TemplateVectorDB[] = [
|
||||
"mongo",
|
||||
"pg",
|
||||
"pinecone",
|
||||
"milvus",
|
||||
"astra",
|
||||
"qdrant",
|
||||
"chroma",
|
||||
"weaviate",
|
||||
];
|
||||
const toolOptions = [
|
||||
"wikipedia.WikipediaToolSpec",
|
||||
"google.GoogleSearchToolSpec",
|
||||
"document_generator",
|
||||
"artifact",
|
||||
];
|
||||
|
||||
const dataSources = [
|
||||
"--example-file",
|
||||
"--web-source https://www.example.com",
|
||||
"--db-source mysql+pymysql://user:pass@localhost:3306/mydb",
|
||||
];
|
||||
|
||||
const observabilityOptions = ["llamatrace", "traceloop"];
|
||||
|
||||
// Test vector databases
|
||||
for (const vectorDb of vectorDbs) {
|
||||
test(`vectorDB: ${vectorDb} ${templateType}`, async () => {
|
||||
const cwd = await createTestDir();
|
||||
const { pyprojectPath } = await createAndCheckLlamaProject({
|
||||
options: {
|
||||
cwd,
|
||||
templateType: "streaming",
|
||||
templateFramework,
|
||||
dataSource: "--example-file",
|
||||
vectorDb,
|
||||
tools: "none",
|
||||
port: 3000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
observability: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
|
||||
if (vectorDb !== "none") {
|
||||
if (vectorDb === "pg") {
|
||||
expect(pyprojectContent).toContain(
|
||||
"llama-index-vector-stores-postgres",
|
||||
);
|
||||
} else {
|
||||
expect(pyprojectContent).toContain(
|
||||
`llama-index-vector-stores-${vectorDb}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// // Test tools
|
||||
for (const tool of toolOptions) {
|
||||
test(`tool: ${tool} ${templateType}`, async () => {
|
||||
const cwd = await createTestDir();
|
||||
const { pyprojectPath } = await createAndCheckLlamaProject({
|
||||
options: {
|
||||
cwd,
|
||||
templateType: "streaming",
|
||||
templateFramework,
|
||||
dataSource: "--example-file",
|
||||
vectorDb: "none",
|
||||
tools: tool,
|
||||
port: 3000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
observability: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
|
||||
if (tool === "wikipedia.WikipediaToolSpec") {
|
||||
expect(pyprojectContent).toContain("wikipedia");
|
||||
}
|
||||
if (tool === "google.GoogleSearchToolSpec") {
|
||||
expect(pyprojectContent).toContain("google");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// // Test data sources
|
||||
for (const dataSource of dataSources) {
|
||||
test(`data source: ${dataSource} ${templateType}`, async () => {
|
||||
const dataSourceType = dataSource.split(" ")[0];
|
||||
const cwd = await createTestDir();
|
||||
const { pyprojectPath } = await createAndCheckLlamaProject({
|
||||
options: {
|
||||
cwd,
|
||||
templateType: "streaming",
|
||||
templateFramework,
|
||||
dataSource,
|
||||
vectorDb: "none",
|
||||
tools: "none",
|
||||
port: 3000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
observability: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const pyprojectContent = fs.readFileSync(pyprojectPath, "utf-8");
|
||||
if (dataSource.includes("--web-source")) {
|
||||
expect(pyprojectContent).toContain("llama-index-readers-web");
|
||||
}
|
||||
if (dataSource.includes("--db-source")) {
|
||||
expect(pyprojectContent).toContain("llama-index-readers-database");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test observability options
|
||||
for (const observability of observabilityOptions) {
|
||||
test.describe(`observability: ${observability} ${templateType}`, async () => {
|
||||
const cwd = await createTestDir();
|
||||
|
||||
const { pyprojectPath } = await createAndCheckLlamaProject({
|
||||
options: {
|
||||
cwd,
|
||||
templateType: "streaming",
|
||||
templateFramework,
|
||||
dataSource: "--example-file",
|
||||
vectorDb: "none",
|
||||
tools: "none",
|
||||
port: 3000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
observability,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test.describe("LlamaIndexServer", async () => {
|
||||
test.skip(templateType !== "llamaindexserver", `skipping llamaindexserver test for ${templateType}`);
|
||||
test.skip(dataSource !== "--example-file", `skipping llamaindexserver test for ${dataSource}`);
|
||||
for (const useCase of useCases) {
|
||||
const cwd = await createTestDir();
|
||||
await createAndCheckLlamaProject({
|
||||
options: {
|
||||
cwd,
|
||||
templateType: "llamaindexserver",
|
||||
templateFramework,
|
||||
dataSource,
|
||||
vectorDb: "none",
|
||||
tools: "none",
|
||||
port: 3000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
observability: undefined,
|
||||
useCase,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function createAndCheckLlamaProject({
|
||||
options,
|
||||
}: {
|
||||
options: RunCreateLlamaOptions;
|
||||
}): Promise<{ pyprojectPath: string; projectPath: string }> {
|
||||
const result = await runCreateLlama(options);
|
||||
const name = result.projectName;
|
||||
const projectPath = path.join(options.cwd, name);
|
||||
|
||||
// Check if the app folder exists
|
||||
expect(fs.existsSync(projectPath)).toBeTruthy();
|
||||
|
||||
// Check if pyproject.toml exists
|
||||
const pyprojectPath = path.join(projectPath, "pyproject.toml");
|
||||
expect(fs.existsSync(pyprojectPath)).toBeTruthy();
|
||||
|
||||
// Modify environment for the command
|
||||
const commandEnv = {
|
||||
...process.env,
|
||||
};
|
||||
|
||||
console.log("Running uv venv...");
|
||||
try {
|
||||
const { stdout: venvStdout, stderr: venvStderr } = await execAsync(
|
||||
"uv venv",
|
||||
{ cwd: projectPath, env: commandEnv },
|
||||
);
|
||||
console.log("uv venv stdout:", venvStdout);
|
||||
console.error("uv venv stderr:", venvStderr);
|
||||
} catch (error) {
|
||||
console.error("Error running uv venv:", error);
|
||||
throw error; // Re-throw error to fail the test
|
||||
}
|
||||
|
||||
console.log("Running uv sync...");
|
||||
try {
|
||||
const { stdout: syncStdout, stderr: syncStderr } = await execAsync(
|
||||
"uv sync --all-extras",
|
||||
{ cwd: projectPath, env: commandEnv },
|
||||
);
|
||||
console.log("uv sync stdout:", syncStdout);
|
||||
console.error("uv sync stderr:", syncStderr);
|
||||
} catch (error) {
|
||||
console.error("Error running uv sync:", error);
|
||||
throw error; // Re-throw error to fail the test
|
||||
}
|
||||
|
||||
console.log("Running uv run mypy ....");
|
||||
try {
|
||||
const { stdout: mypyStdout, stderr: mypyStderr } = await execAsync(
|
||||
"uv run mypy .",
|
||||
{ cwd: projectPath, env: commandEnv },
|
||||
);
|
||||
console.log("uv run mypy stdout:", mypyStdout);
|
||||
console.error("uv run mypy stderr:", mypyStderr);
|
||||
// Assuming mypy success means no output or specific success message
|
||||
// Adjust checks based on actual expected mypy output
|
||||
} catch (error) {
|
||||
console.error("Error running mypy:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// If we reach this point without throwing an error, the test passes
|
||||
expect(true).toBeTruthy();
|
||||
|
||||
return { pyprojectPath, projectPath };
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { ChildProcess, execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import type {
|
||||
TemplateFramework,
|
||||
TemplatePostInstallAction,
|
||||
TemplateUI,
|
||||
} from "../../helpers";
|
||||
import { createTestDir, runCreateLlama, type AppType } from "../utils";
|
||||
|
||||
const templateFramework: TemplateFramework = process.env.FRAMEWORK
|
||||
? (process.env.FRAMEWORK as TemplateFramework)
|
||||
: "fastapi";
|
||||
const dataSource: string = process.env.DATASOURCE
|
||||
? (process.env.DATASOURCE as string)
|
||||
: "--example-file";
|
||||
const llamaCloudProjectName = "create-llama";
|
||||
const llamaCloudIndexName = "e2e-test";
|
||||
|
||||
const templateUI: TemplateUI = "shadcn";
|
||||
const templatePostInstallAction: TemplatePostInstallAction = "runApp";
|
||||
const appType: AppType = "--frontend";
|
||||
const userMessage = "Write a blog post about physical standards for letters";
|
||||
const templateUseCases = [
|
||||
"agentic_rag",
|
||||
"financial_report",
|
||||
"deep_research",
|
||||
"code_generator",
|
||||
];
|
||||
const ejectDir = "next";
|
||||
|
||||
for (const useCase of templateUseCases) {
|
||||
test.describe(`Test use case ${useCase} ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
|
||||
test.skip(
|
||||
dataSource === "--no-files" || templateFramework === "express",
|
||||
"The llamaindexserver template currently only works with nextjs, fastapi. We also only run on Linux to speed up tests.",
|
||||
);
|
||||
const useLlamaParse = dataSource === "--llamacloud";
|
||||
let port: number;
|
||||
let cwd: string;
|
||||
let name: string;
|
||||
let appProcess: ChildProcess;
|
||||
// Only test without using vector db for now
|
||||
const vectorDb = "none";
|
||||
|
||||
test.beforeAll(async () => {
|
||||
port = Math.floor(Math.random() * 10000) + 10000;
|
||||
cwd = await createTestDir();
|
||||
const result = await runCreateLlama({
|
||||
cwd,
|
||||
templateType: "llamaindexserver",
|
||||
templateFramework,
|
||||
dataSource,
|
||||
vectorDb,
|
||||
port,
|
||||
postInstallAction: templatePostInstallAction,
|
||||
templateUI,
|
||||
appType,
|
||||
useCase,
|
||||
llamaCloudProjectName,
|
||||
llamaCloudIndexName,
|
||||
useLlamaParse,
|
||||
});
|
||||
name = result.projectName;
|
||||
appProcess = result.appProcess;
|
||||
});
|
||||
|
||||
test("App folder should exist", async () => {
|
||||
const dirExists = fs.existsSync(path.join(cwd, name));
|
||||
expect(dirExists).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Frontend should have a title", async ({ page }) => {
|
||||
test.skip(
|
||||
templatePostInstallAction !== "runApp" ||
|
||||
templateFramework === "express",
|
||||
);
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await expect(page.getByText("Built by LlamaIndex")).toBeVisible({
|
||||
timeout: 5 * 60 * 1000,
|
||||
});
|
||||
});
|
||||
|
||||
test("Frontend should be able to submit a message and receive the start of a streamed response", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip(
|
||||
templatePostInstallAction !== "runApp" ||
|
||||
useCase === "financial_report" ||
|
||||
useCase === "deep_research" ||
|
||||
templateFramework === "express",
|
||||
"Skip chat tests for financial report and deep research.",
|
||||
);
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await page.fill("form textarea", userMessage);
|
||||
|
||||
const responsePromise = page.waitForResponse((res) =>
|
||||
res.url().includes("/api/chat"),
|
||||
);
|
||||
|
||||
await page.click("form button[type=submit]");
|
||||
|
||||
const response = await responsePromise;
|
||||
console.log(`Response status: ${response.status()}`);
|
||||
const responseBody = await response
|
||||
.text()
|
||||
.catch((e) => `Error reading body: ${e}`);
|
||||
console.log(`Response body: ${responseBody}`);
|
||||
|
||||
expect(response.ok()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Should successfully eject, install dependencies and build without errors", async () => {
|
||||
test.skip(
|
||||
templateFramework !== "nextjs" ||
|
||||
useCase !== "code_generator" ||
|
||||
dataSource === "--llamacloud",
|
||||
"Eject test only applies to Next.js framework, code generator use case, and non-llamacloud",
|
||||
);
|
||||
|
||||
// Run eject command
|
||||
execSync("npm run eject", { cwd: path.join(cwd, name) });
|
||||
|
||||
// Verify next directory exists
|
||||
const nextDirExists = fs.existsSync(path.join(cwd, name, ejectDir));
|
||||
expect(nextDirExists).toBeTruthy();
|
||||
|
||||
// Install dependencies in next directory
|
||||
execSync("npm install", { cwd: path.join(cwd, name, ejectDir) });
|
||||
|
||||
// Run build
|
||||
execSync("npm run build", { cwd: path.join(cwd, name, ejectDir) });
|
||||
});
|
||||
|
||||
// clean processes
|
||||
test.afterAll(async () => {
|
||||
appProcess?.kill();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { ChildProcess } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { TemplateFramework, TemplateUseCase } from "../../helpers";
|
||||
import { createTestDir, runCreateLlama } from "../utils";
|
||||
|
||||
const templateFramework: TemplateFramework = process.env.FRAMEWORK
|
||||
? (process.env.FRAMEWORK as TemplateFramework)
|
||||
: "fastapi";
|
||||
const dataSource: string = process.env.DATASOURCE
|
||||
? process.env.DATASOURCE
|
||||
: "--example-file";
|
||||
const templateUseCases: TemplateUseCase[] = ["extractor", "contract_review"];
|
||||
|
||||
// The reflex template currently only works with FastAPI and files (and not on Windows)
|
||||
if (
|
||||
process.platform !== "win32" &&
|
||||
templateFramework === "fastapi" &&
|
||||
dataSource === "--example-file"
|
||||
) {
|
||||
for (const useCase of templateUseCases) {
|
||||
test.describe(`Test reflex template ${useCase} ${templateFramework} ${dataSource}`, async () => {
|
||||
let appPort: number;
|
||||
let name: string;
|
||||
let appProcess: ChildProcess;
|
||||
let cwd: string;
|
||||
|
||||
// Create reflex app
|
||||
test.beforeAll(async () => {
|
||||
cwd = await createTestDir();
|
||||
appPort = Math.floor(Math.random() * 10000) + 10000;
|
||||
const result = await runCreateLlama({
|
||||
cwd,
|
||||
templateType: "reflex",
|
||||
templateFramework: "fastapi",
|
||||
dataSource: "--example-file",
|
||||
vectorDb: "none",
|
||||
port: appPort,
|
||||
postInstallAction: "runApp",
|
||||
useCase,
|
||||
});
|
||||
name = result.projectName;
|
||||
appProcess = result.appProcess;
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
appProcess.kill();
|
||||
});
|
||||
|
||||
test("App folder should exist", async () => {
|
||||
const dirExists = fs.existsSync(path.join(cwd, name));
|
||||
expect(dirExists).toBeTruthy();
|
||||
});
|
||||
test("Frontend should have a title", async ({ page }) => {
|
||||
await page.goto(`http://localhost:${appPort}`);
|
||||
await expect(page.getByText("Built by LlamaIndex")).toBeVisible({
|
||||
timeout: 2000 * 60,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
+9
-8
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { ChildProcess } from "child_process";
|
||||
import fs from "fs";
|
||||
@@ -22,7 +21,7 @@ const templatePostInstallAction: TemplatePostInstallAction = "runApp";
|
||||
const llamaCloudProjectName = "create-llama";
|
||||
const llamaCloudIndexName = "e2e-test";
|
||||
|
||||
const appType: AppType = templateFramework === "nextjs" ? "" : "--frontend";
|
||||
const appType: AppType = templateFramework === "fastapi" ? "--frontend" : "";
|
||||
const userMessage =
|
||||
dataSource !== "--no-files" ? "Physical standard for letters" : "Hello";
|
||||
|
||||
@@ -35,7 +34,6 @@ test.describe(`Test streaming template ${templateFramework} ${dataSource} ${temp
|
||||
}
|
||||
|
||||
let port: number;
|
||||
let externalPort: number;
|
||||
let cwd: string;
|
||||
let name: string;
|
||||
let appProcess: ChildProcess;
|
||||
@@ -44,7 +42,6 @@ test.describe(`Test streaming template ${templateFramework} ${dataSource} ${temp
|
||||
|
||||
test.beforeAll(async () => {
|
||||
port = Math.floor(Math.random() * 10000) + 10000;
|
||||
externalPort = port + 1;
|
||||
cwd = await createTestDir();
|
||||
const result = await runCreateLlama({
|
||||
cwd,
|
||||
@@ -53,7 +50,6 @@ test.describe(`Test streaming template ${templateFramework} ${dataSource} ${temp
|
||||
dataSource,
|
||||
vectorDb,
|
||||
port,
|
||||
externalPort,
|
||||
postInstallAction: templatePostInstallAction,
|
||||
templateUI,
|
||||
appType,
|
||||
@@ -68,8 +64,11 @@ test.describe(`Test streaming template ${templateFramework} ${dataSource} ${temp
|
||||
const dirExists = fs.existsSync(path.join(cwd, name));
|
||||
expect(dirExists).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Frontend should have a title", async ({ page }) => {
|
||||
test.skip(templatePostInstallAction !== "runApp");
|
||||
test.skip(
|
||||
templatePostInstallAction !== "runApp" || templateFramework === "express",
|
||||
);
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
|
||||
});
|
||||
@@ -77,7 +76,9 @@ test.describe(`Test streaming template ${templateFramework} ${dataSource} ${temp
|
||||
test("Frontend should be able to submit a message and receive a response", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip(templatePostInstallAction !== "runApp");
|
||||
test.skip(
|
||||
templatePostInstallAction !== "runApp" || templateFramework === "express",
|
||||
);
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await page.fill("form textarea", userMessage);
|
||||
const [response] = await Promise.all([
|
||||
@@ -102,7 +103,7 @@ test.describe(`Test streaming template ${templateFramework} ${dataSource} ${temp
|
||||
test.skip(templatePostInstallAction !== "runApp");
|
||||
test.skip(templateFramework === "nextjs");
|
||||
const response = await request.post(
|
||||
`http://localhost:${externalPort}/api/chat/request`,
|
||||
`http://localhost:${port}/api/chat/request`,
|
||||
{
|
||||
data: {
|
||||
messages: [
|
||||
@@ -0,0 +1,161 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { exec } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import util from "util";
|
||||
import {
|
||||
TemplateFramework,
|
||||
TemplateType,
|
||||
TemplateUseCase,
|
||||
TemplateVectorDB,
|
||||
} from "../../helpers/types";
|
||||
import { createTestDir, runCreateLlama } from "../utils";
|
||||
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
const templateFramework: TemplateFramework = process.env.FRAMEWORK
|
||||
? (process.env.FRAMEWORK as TemplateFramework)
|
||||
: "nextjs";
|
||||
const templateType: TemplateType = process.env.TEMPLATE_TYPE
|
||||
? (process.env.TEMPLATE_TYPE as TemplateType)
|
||||
: "llamaindexserver";
|
||||
const useCases: TemplateUseCase[] = [
|
||||
"agentic_rag",
|
||||
"deep_research",
|
||||
"financial_report",
|
||||
"code_generator",
|
||||
"document_generator",
|
||||
];
|
||||
const dataSource: string = process.env.DATASOURCE
|
||||
? process.env.DATASOURCE
|
||||
: "--example-file";
|
||||
|
||||
// vectorDBs combinations to test
|
||||
const vectorDbs: TemplateVectorDB[] = [
|
||||
"mongo",
|
||||
"pg",
|
||||
"qdrant",
|
||||
"pinecone",
|
||||
"milvus",
|
||||
"astra",
|
||||
"chroma",
|
||||
"llamacloud",
|
||||
"weaviate",
|
||||
];
|
||||
|
||||
test.describe("Test resolve TS dependencies", () => {
|
||||
test.describe.configure({ retries: 0 });
|
||||
|
||||
// Test vector DBs without LlamaParse
|
||||
for (const vectorDb of vectorDbs) {
|
||||
const optionDescription = `templateType: ${templateType}, vectorDb: ${vectorDb}, dataSource: ${dataSource}`;
|
||||
|
||||
test(`Vector DB test - ${optionDescription}`, async () => {
|
||||
// skip vectordb test for llamaindexserver
|
||||
test.skip(
|
||||
templateType === "llamaindexserver",
|
||||
"skipping vectorDB test for llamaindexserver",
|
||||
);
|
||||
|
||||
await runTest({
|
||||
templateType: templateType,
|
||||
useLlamaParse: false, // Disable LlamaParse for vectorDB test
|
||||
vectorDb: vectorDb,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// No vectorDB, with LlamaParse and useCase
|
||||
// Only need to test use case with example data source
|
||||
if (dataSource === "--example-file") {
|
||||
for (const useCase of useCases) {
|
||||
const optionDescription = `templateType: ${templateType}, useCase: ${useCase}`;
|
||||
test.describe(`useCase test - ${optionDescription}`, () => {
|
||||
test.skip(
|
||||
templateType === "streaming",
|
||||
"Skipping use case test for streaming template.",
|
||||
);
|
||||
test(`no llamaParse - ${optionDescription}`, async () => {
|
||||
await runTest({
|
||||
templateType: templateType,
|
||||
useLlamaParse: false,
|
||||
useCase: useCase,
|
||||
});
|
||||
});
|
||||
// Skipping llamacloud for the use case doesn't use index.
|
||||
if (useCase !== "code_generator" && useCase !== "document_generator") {
|
||||
test(`llamaParse - ${optionDescription}`, async () => {
|
||||
await runTest({
|
||||
templateType: templateType,
|
||||
useLlamaParse: true,
|
||||
useCase: useCase,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function runTest(options: {
|
||||
templateType: TemplateType;
|
||||
useLlamaParse: boolean;
|
||||
useCase?: TemplateUseCase;
|
||||
vectorDb?: TemplateVectorDB;
|
||||
}) {
|
||||
const cwd = await createTestDir();
|
||||
|
||||
const result = await runCreateLlama({
|
||||
cwd: cwd,
|
||||
templateType: options.templateType,
|
||||
templateFramework: templateFramework,
|
||||
dataSource: dataSource,
|
||||
vectorDb: options.vectorDb ?? "none",
|
||||
port: 3000,
|
||||
postInstallAction: "none",
|
||||
templateUI: undefined,
|
||||
appType: templateFramework === "nextjs" ? "" : "--no-frontend",
|
||||
llamaCloudProjectName: undefined,
|
||||
llamaCloudIndexName: undefined,
|
||||
tools: undefined,
|
||||
useLlamaParse: options.useLlamaParse,
|
||||
useCase: options.useCase,
|
||||
});
|
||||
const name = result.projectName;
|
||||
|
||||
// Check if the app folder exists
|
||||
const appDir = path.join(cwd, name);
|
||||
const dirExists = fs.existsSync(appDir);
|
||||
expect(dirExists).toBeTruthy();
|
||||
|
||||
// Install dependencies using pnpm
|
||||
try {
|
||||
const { stderr: installStderr } = await execAsync(
|
||||
"pnpm install --prefer-offline --ignore-workspace",
|
||||
{
|
||||
cwd: appDir,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error installing dependencies:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Run tsc type check and capture the output
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(
|
||||
"pnpm exec tsc -b --diagnostics",
|
||||
{
|
||||
cwd: appDir,
|
||||
},
|
||||
);
|
||||
// Check if there's any error output
|
||||
expect(stderr).toBeFalsy();
|
||||
|
||||
// Log the stdout for debugging purposes
|
||||
console.log("TypeScript type-check output:", stdout);
|
||||
} catch (error) {
|
||||
console.error("Error running tsc:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ export type RunCreateLlamaOptions = {
|
||||
dataSource: string;
|
||||
vectorDb: TemplateVectorDB;
|
||||
port: number;
|
||||
externalPort: number;
|
||||
postInstallAction: TemplatePostInstallAction;
|
||||
templateUI?: TemplateUI;
|
||||
appType?: AppType;
|
||||
@@ -34,6 +33,7 @@ export type RunCreateLlamaOptions = {
|
||||
tools?: string;
|
||||
useLlamaParse?: boolean;
|
||||
observability?: string;
|
||||
useCase?: string;
|
||||
};
|
||||
|
||||
export async function runCreateLlama({
|
||||
@@ -43,7 +43,6 @@ export async function runCreateLlama({
|
||||
dataSource,
|
||||
vectorDb,
|
||||
port,
|
||||
externalPort,
|
||||
postInstallAction,
|
||||
templateUI,
|
||||
appType,
|
||||
@@ -52,6 +51,7 @@ export async function runCreateLlama({
|
||||
tools,
|
||||
useLlamaParse,
|
||||
observability,
|
||||
useCase,
|
||||
}: RunCreateLlamaOptions): Promise<CreateLlamaResult> {
|
||||
if (!process.env.OPENAI_API_KEY || !process.env.LLAMA_CLOUD_API_KEY) {
|
||||
throw new Error(
|
||||
@@ -67,8 +67,8 @@ export async function runCreateLlama({
|
||||
].join("-");
|
||||
|
||||
// Handle different data source types
|
||||
let dataSourceArgs = [];
|
||||
if (dataSource.includes("--web-source" || "--db-source")) {
|
||||
const dataSourceArgs = [];
|
||||
if (dataSource.includes("--web-source")) {
|
||||
const webSource = dataSource.split(" ")[1];
|
||||
dataSourceArgs.push("--web-source", webSource);
|
||||
} else if (dataSource.includes("--db-source")) {
|
||||
@@ -88,21 +88,15 @@ export async function runCreateLlama({
|
||||
...dataSourceArgs,
|
||||
"--vector-db",
|
||||
vectorDb,
|
||||
"--open-ai-key",
|
||||
process.env.OPENAI_API_KEY,
|
||||
"--use-pnpm",
|
||||
"--use-npm",
|
||||
"--port",
|
||||
port,
|
||||
"--external-port",
|
||||
externalPort,
|
||||
"--post-install-action",
|
||||
postInstallAction,
|
||||
"--tools",
|
||||
tools ?? "none",
|
||||
"--observability",
|
||||
"none",
|
||||
"--llama-cloud-key",
|
||||
process.env.LLAMA_CLOUD_API_KEY,
|
||||
];
|
||||
|
||||
if (templateUI) {
|
||||
@@ -119,6 +113,14 @@ export async function runCreateLlama({
|
||||
if (observability) {
|
||||
commandArgs.push("--observability", observability);
|
||||
}
|
||||
if (
|
||||
(templateType === "multiagent" ||
|
||||
templateType === "reflex" ||
|
||||
templateType === "llamaindexserver") &&
|
||||
useCase
|
||||
) {
|
||||
commandArgs.push("--use-case", useCase);
|
||||
}
|
||||
|
||||
const command = commandArgs.join(" ");
|
||||
console.log(`running command '${command}' in ${cwd}`);
|
||||
@@ -141,12 +143,7 @@ export async function runCreateLlama({
|
||||
|
||||
// Wait for app to start
|
||||
if (postInstallAction === "runApp") {
|
||||
await checkAppHasStarted(
|
||||
appType === "--frontend",
|
||||
templateFramework,
|
||||
port,
|
||||
externalPort,
|
||||
);
|
||||
await waitPorts([port]);
|
||||
} else if (postInstallAction === "dependencies") {
|
||||
await waitForProcess(appProcess, 1000 * 60); // wait 1 min for dependencies to be resolved
|
||||
} else {
|
||||
@@ -166,19 +163,6 @@ export async function createTestDir() {
|
||||
return cwd;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-params
|
||||
async function checkAppHasStarted(
|
||||
frontend: boolean,
|
||||
framework: TemplateFramework,
|
||||
port: number,
|
||||
externalPort: number,
|
||||
) {
|
||||
const portsToWait = frontend
|
||||
? [port, externalPort]
|
||||
: [framework === "nextjs" ? port : externalPort];
|
||||
await waitPorts(portsToWait);
|
||||
}
|
||||
|
||||
async function waitPorts(ports: number[]): Promise<void> {
|
||||
const waitForPort = async (port: number): Promise<void> => {
|
||||
await waitPort({
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { async as glob } from "fast-glob";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -61,6 +60,9 @@ export const assetRelocator = (name: string) => {
|
||||
case "README-template.md": {
|
||||
return "README.md";
|
||||
}
|
||||
case "vscode_settings.json": {
|
||||
return "settings.json";
|
||||
}
|
||||
default: {
|
||||
return name;
|
||||
}
|
||||
@@ -11,6 +11,47 @@ export const EXAMPLE_FILE: TemplateDataSource = {
|
||||
},
|
||||
};
|
||||
|
||||
export const EXAMPLE_10K_SEC_FILES: TemplateDataSource[] = [
|
||||
{
|
||||
type: "file",
|
||||
config: {
|
||||
url: new URL(
|
||||
"https://s2.q4cdn.com/470004039/files/doc_earnings/2023/q4/filing/_10-K-Q4-2023-As-Filed.pdf",
|
||||
),
|
||||
filename: "apple_10k_report.pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "file",
|
||||
config: {
|
||||
url: new URL(
|
||||
"https://ir.tesla.com/_flysystem/s3/sec/000162828024002390/tsla-20231231-gen.pdf",
|
||||
),
|
||||
filename: "tesla_10k_report.pdf",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const EXAMPLE_GDPR: TemplateDataSource = {
|
||||
type: "file",
|
||||
config: {
|
||||
url: new URL(
|
||||
"https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:32016R0679",
|
||||
),
|
||||
filename: "gdpr.pdf",
|
||||
},
|
||||
};
|
||||
|
||||
export const AI_REPORTS: TemplateDataSource = {
|
||||
type: "file",
|
||||
config: {
|
||||
url: new URL(
|
||||
"https://www.europarl.europa.eu/RegData/etudes/ATAG/2024/760392/EPRS_ATA(2024)760392_EN.pdf",
|
||||
),
|
||||
filename: "EPRS_ATA_2024_760392_EN.pdf",
|
||||
},
|
||||
};
|
||||
|
||||
export function getDataSources(
|
||||
files?: string,
|
||||
exampleFile?: boolean,
|
||||
@@ -13,6 +13,12 @@ import {
|
||||
|
||||
import { TSYSTEMS_LLMHUB_API_URL } from "./providers/llmhub";
|
||||
|
||||
const DEFAULT_SYSTEM_PROMPT =
|
||||
"You are a helpful assistant who helps users with their questions.";
|
||||
|
||||
const DATA_SOURCES_PROMPT =
|
||||
"You have access to a knowledge base including the facts that you should start with to find the answer for the user question. Use the query engine tool to retrieve the facts from the knowledge base.";
|
||||
|
||||
export type EnvVar = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
@@ -38,6 +44,7 @@ const renderEnvVar = (envVars: EnvVar[]): string => {
|
||||
const getVectorDBEnvs = (
|
||||
vectorDb?: TemplateVectorDB,
|
||||
framework?: TemplateFramework,
|
||||
template?: TemplateType,
|
||||
): EnvVar[] => {
|
||||
if (!vectorDb || !framework) {
|
||||
return [];
|
||||
@@ -162,7 +169,7 @@ const getVectorDBEnvs = (
|
||||
description:
|
||||
"The organization ID for the LlamaCloud project (uses default organization if not specified)",
|
||||
},
|
||||
...(framework === "nextjs"
|
||||
...(framework === "nextjs" && template !== "llamaindexserver"
|
||||
? // activate index selector per default (not needed for non-NextJS backends as it's handled by createFrontendEnvFile)
|
||||
[
|
||||
{
|
||||
@@ -174,7 +181,7 @@ const getVectorDBEnvs = (
|
||||
]
|
||||
: []),
|
||||
];
|
||||
case "chroma":
|
||||
case "chroma": {
|
||||
const envs = [
|
||||
{
|
||||
name: "CHROMA_COLLECTION",
|
||||
@@ -199,6 +206,7 @@ Otherwise, use CHROMA_HOST and CHROMA_PORT config above`,
|
||||
});
|
||||
}
|
||||
return envs;
|
||||
}
|
||||
case "weaviate":
|
||||
return [
|
||||
{
|
||||
@@ -217,7 +225,15 @@ Otherwise, use CHROMA_HOST and CHROMA_PORT config above`,
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
return template !== "llamaindexserver"
|
||||
? [
|
||||
{
|
||||
name: "STORAGE_CACHE_DIR",
|
||||
description: "The directory to store the local storage cache.",
|
||||
value: ".cache",
|
||||
},
|
||||
]
|
||||
: [];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -336,6 +352,20 @@ const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(modelConfig.provider === "huggingface"
|
||||
? [
|
||||
{
|
||||
name: "EMBEDDING_BACKEND",
|
||||
description:
|
||||
"The backend to use for the Sentence Transformers embedding model, either 'torch', 'onnx', or 'openvino'. Defaults to 'onnx'.",
|
||||
},
|
||||
{
|
||||
name: "EMBEDDING_TRUST_REMOTE_CODE",
|
||||
description:
|
||||
"Whether to trust remote code for the embedding model, required for some models with custom code.",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(modelConfig.provider === "t-systems"
|
||||
? [
|
||||
{
|
||||
@@ -356,37 +386,48 @@ const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
|
||||
|
||||
const getFrameworkEnvs = (
|
||||
framework: TemplateFramework,
|
||||
template: TemplateType,
|
||||
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`,
|
||||
},
|
||||
];
|
||||
const result: EnvVar[] =
|
||||
template !== "llamaindexserver"
|
||||
? [
|
||||
{
|
||||
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",
|
||||
description: "The address to start the backend app.",
|
||||
description: "The address to start the FastAPI app.",
|
||||
value: "0.0.0.0",
|
||||
},
|
||||
{
|
||||
name: "APP_PORT",
|
||||
description: "The port to start the backend app.",
|
||||
description: "The port to start the FastAPI app.",
|
||||
value: sPort,
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
if (framework === "nextjs" && template !== "llamaindexserver") {
|
||||
result.push({
|
||||
name: "NEXT_PUBLIC_CHAT_API",
|
||||
description:
|
||||
"The API for the chat endpoint. Set when using a custom backend (e.g. Express). Use full URL like http://localhost:8000/api/chat",
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -422,9 +463,6 @@ const getSystemPromptEnv = (
|
||||
dataSources?: TemplateDataSource[],
|
||||
template?: TemplateType,
|
||||
): EnvVar[] => {
|
||||
const defaultSystemPrompt =
|
||||
"You are a helpful assistant who helps users with their questions.";
|
||||
|
||||
const systemPromptEnv: EnvVar[] = [];
|
||||
// build tool system prompt by merging all tool system prompts
|
||||
// multiagent template doesn't need system prompt
|
||||
@@ -439,9 +477,12 @@ const getSystemPromptEnv = (
|
||||
}
|
||||
});
|
||||
|
||||
const systemPrompt = toolSystemPrompt
|
||||
? `\"${toolSystemPrompt}\"`
|
||||
: defaultSystemPrompt;
|
||||
const systemPrompt =
|
||||
'"' +
|
||||
DEFAULT_SYSTEM_PROMPT +
|
||||
(dataSources?.length ? `\n${DATA_SOURCES_PROMPT}` : "") +
|
||||
(toolSystemPrompt ? `\n${toolSystemPrompt}` : "") +
|
||||
'"';
|
||||
|
||||
systemPromptEnv.push({
|
||||
name: "SYSTEM_PROMPT",
|
||||
@@ -492,7 +533,7 @@ Here is the conversation history
|
||||
---------------------
|
||||
{conversation}
|
||||
---------------------
|
||||
Given the conversation history, please give me 3 questions that you might ask next!
|
||||
Given the conversation history, please give me 3 questions that user might ask next!
|
||||
Your answer should be wrapped in three sticks which follows the following format:
|
||||
\`\`\`
|
||||
<question 1>
|
||||
@@ -533,28 +574,44 @@ export const createBackendEnvFile = async (
|
||||
| "framework"
|
||||
| "dataSources"
|
||||
| "template"
|
||||
| "externalPort"
|
||||
| "port"
|
||||
| "tools"
|
||||
| "observability"
|
||||
| "useLlamaParse"
|
||||
>,
|
||||
) => {
|
||||
// Init env values
|
||||
const envFileName = ".env";
|
||||
const envVars: EnvVar[] = [
|
||||
{
|
||||
name: "LLAMA_CLOUD_API_KEY",
|
||||
description: `The Llama Cloud API key.`,
|
||||
value: opts.llamaCloudKey,
|
||||
},
|
||||
// Add environment variables of each component
|
||||
...getModelEnvs(opts.modelConfig),
|
||||
...getEngineEnvs(),
|
||||
...getVectorDBEnvs(opts.vectorDb, opts.framework),
|
||||
...getFrameworkEnvs(opts.framework, opts.externalPort),
|
||||
...(opts.useLlamaParse
|
||||
? [
|
||||
{
|
||||
name: "LLAMA_CLOUD_API_KEY",
|
||||
description: `The Llama Cloud API key.`,
|
||||
value: opts.llamaCloudKey,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...getVectorDBEnvs(opts.vectorDb, opts.framework, opts.template),
|
||||
...getToolEnvs(opts.tools),
|
||||
...getTemplateEnvs(opts.template),
|
||||
...getObservabilityEnvs(opts.observability),
|
||||
...getSystemPromptEnv(opts.tools, opts.dataSources, opts.template),
|
||||
...getFrameworkEnvs(opts.framework, opts.template, opts.port),
|
||||
// Add environment variables of each component
|
||||
...(opts.template === "llamaindexserver"
|
||||
? [
|
||||
{
|
||||
name: "OPENAI_API_KEY",
|
||||
description: "The OpenAI API key to use.",
|
||||
value: opts.modelConfig.apiKey,
|
||||
},
|
||||
]
|
||||
: [
|
||||
// don't use this stuff for llama-indexserver
|
||||
...getModelEnvs(opts.modelConfig),
|
||||
...getEngineEnvs(),
|
||||
...getTemplateEnvs(opts.template),
|
||||
...getObservabilityEnvs(opts.observability),
|
||||
...getSystemPromptEnv(opts.tools, opts.dataSources, opts.template),
|
||||
]),
|
||||
];
|
||||
// Render and write env file
|
||||
const content = renderEnvVar(envVars);
|
||||
@@ -565,18 +622,10 @@ export const createBackendEnvFile = async (
|
||||
export const createFrontendEnvFile = async (
|
||||
root: string,
|
||||
opts: {
|
||||
customApiPath?: string;
|
||||
vectorDb?: TemplateVectorDB;
|
||||
},
|
||||
) => {
|
||||
const defaultFrontendEnvs = [
|
||||
{
|
||||
name: "NEXT_PUBLIC_CHAT_API",
|
||||
description: "The backend API for chat endpoint.",
|
||||
value: opts.customApiPath
|
||||
? opts.customApiPath
|
||||
: "http://localhost:8000/api/chat",
|
||||
},
|
||||
{
|
||||
name: "NEXT_PUBLIC_USE_LLAMACLOUD",
|
||||
description: "Let's the user change indexes in LlamaCloud projects",
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { callPackageManager } from "./install";
|
||||
|
||||
import path from "path";
|
||||
import { cyan } from "picocolors";
|
||||
import picocolors, { cyan } from "picocolors";
|
||||
|
||||
import fsExtra from "fs-extra";
|
||||
import { writeLoadersConfig } from "./datasources";
|
||||
@@ -9,7 +9,6 @@ import { createBackendEnvFile, createFrontendEnvFile } from "./env-variables";
|
||||
import { PackageManager } from "./get-pkg-manager";
|
||||
import { installLlamapackProject } from "./llama-pack";
|
||||
import { makeDir } from "./make-dir";
|
||||
import { isHavingPoetryLockFile, tryPoetryRun } from "./poetry";
|
||||
import { installPythonTemplate } from "./python";
|
||||
import { downloadAndExtractRepo } from "./repo";
|
||||
import { ConfigFileType, writeToolsConfig } from "./tools";
|
||||
@@ -19,9 +18,11 @@ import {
|
||||
ModelConfig,
|
||||
TemplateDataSource,
|
||||
TemplateFramework,
|
||||
TemplateUseCase,
|
||||
TemplateVectorDB,
|
||||
} from "./types";
|
||||
import { installTSTemplate } from "./typescript";
|
||||
import { isHavingUvLockFile, tryUvRun } from "./uv";
|
||||
|
||||
const checkForGenerateScript = (
|
||||
modelConfig: ModelConfig,
|
||||
@@ -41,7 +42,11 @@ const checkForGenerateScript = (
|
||||
missingSettings.push("your LLAMA_CLOUD_API_KEY");
|
||||
}
|
||||
|
||||
if (vectorDb !== "none" && vectorDb !== "llamacloud") {
|
||||
if (
|
||||
vectorDb !== undefined &&
|
||||
vectorDb !== "none" &&
|
||||
vectorDb !== "llamacloud"
|
||||
) {
|
||||
missingSettings.push("your Vector DB environment variables");
|
||||
}
|
||||
|
||||
@@ -56,11 +61,12 @@ async function generateContextData(
|
||||
vectorDb?: TemplateVectorDB,
|
||||
llamaCloudKey?: string,
|
||||
useLlamaParse?: boolean,
|
||||
useCase?: TemplateUseCase,
|
||||
) {
|
||||
if (packageManager) {
|
||||
const runGenerate = `${cyan(
|
||||
framework === "fastapi"
|
||||
? "poetry run generate"
|
||||
? "uv run generate"
|
||||
: `${packageManager} run generate`,
|
||||
)}`;
|
||||
|
||||
@@ -74,28 +80,45 @@ async function generateContextData(
|
||||
if (!missingSettings.length) {
|
||||
// If all the required environment variables are set, run the generate script
|
||||
if (framework === "fastapi") {
|
||||
if (isHavingPoetryLockFile()) {
|
||||
if (isHavingUvLockFile()) {
|
||||
console.log(`Running ${runGenerate} to generate the context data.`);
|
||||
const result = tryPoetryRun("poetry run generate");
|
||||
const result = tryUvRun("generate");
|
||||
if (!result) {
|
||||
console.log(`Failed to run ${runGenerate}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Generated context data`);
|
||||
return;
|
||||
} else {
|
||||
console.log(
|
||||
picocolors.yellow(
|
||||
`\nWarning: uv.lock not found. Dependency installation might be incomplete. Skipping context generation.\nIf dependencies were installed, try running '${runGenerate}' manually.\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(`Running ${runGenerate} to generate the context data.`);
|
||||
await callPackageManager(packageManager, true, ["run", "generate"]);
|
||||
const shouldRunGenerate =
|
||||
useCase !== "code_generator" && useCase !== "document_generator"; // Artifact use case doesn't use index.
|
||||
|
||||
if (shouldRunGenerate) {
|
||||
await callPackageManager(packageManager, true, ["run", "generate"]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const settingsMessage = `After setting ${missingSettings.join(" and ")}, run ${runGenerate} to generate the context data.`;
|
||||
console.log(`\n${settingsMessage}\n\n`);
|
||||
console.log(picocolors.yellow(`\n${settingsMessage}\n\n`));
|
||||
}
|
||||
}
|
||||
|
||||
const downloadFile = async (url: string, destPath: string) => {
|
||||
const response = await fetch(url);
|
||||
const fileBuffer = await response.arrayBuffer();
|
||||
await fsExtra.writeFile(destPath, new Uint8Array(fileBuffer));
|
||||
};
|
||||
|
||||
const prepareContextData = async (
|
||||
root: string,
|
||||
dataSources: TemplateDataSource[],
|
||||
@@ -103,12 +126,29 @@ const prepareContextData = async (
|
||||
await makeDir(path.join(root, "data"));
|
||||
for (const dataSource of dataSources) {
|
||||
const dataSourceConfig = dataSource?.config as FileSourceConfig;
|
||||
// Copy local data
|
||||
const dataPath = dataSourceConfig.path;
|
||||
|
||||
const destPath = path.join(root, "data", path.basename(dataPath));
|
||||
console.log("Copying data from path:", dataPath);
|
||||
await fsExtra.copy(dataPath, destPath);
|
||||
// If the path is URLs, download the data and save it to the data directory
|
||||
if ("url" in dataSourceConfig) {
|
||||
console.log(
|
||||
"Downloading file from URL:",
|
||||
dataSourceConfig.url.toString(),
|
||||
);
|
||||
const destPath = path.join(
|
||||
root,
|
||||
"data",
|
||||
dataSourceConfig.filename ??
|
||||
path.basename(dataSourceConfig.url.toString()),
|
||||
);
|
||||
await downloadFile(dataSourceConfig.url.toString(), destPath);
|
||||
} else {
|
||||
// Copy local data
|
||||
console.log("Copying data from path:", dataSourceConfig.path);
|
||||
const destPath = path.join(
|
||||
root,
|
||||
"data",
|
||||
path.basename(dataSourceConfig.path),
|
||||
);
|
||||
await fsExtra.copy(dataSourceConfig.path, destPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -143,6 +183,17 @@ export const installTemplate = async (
|
||||
|
||||
if (props.framework === "fastapi") {
|
||||
await installPythonTemplate(props);
|
||||
} else {
|
||||
await installTSTemplate(props);
|
||||
}
|
||||
|
||||
// write configurations
|
||||
if (props.template !== "llamaindexserver") {
|
||||
await writeToolsConfig(
|
||||
props.root,
|
||||
props.tools,
|
||||
props.framework === "fastapi" ? ConfigFileType.YAML : ConfigFileType.JSON,
|
||||
);
|
||||
if (props.vectorDb !== "llamacloud") {
|
||||
// write loaders configuration (currently Python only)
|
||||
// not needed for LlamaCloud as it has its own loaders
|
||||
@@ -152,26 +203,13 @@ export const installTemplate = async (
|
||||
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.
|
||||
if (
|
||||
props.template === "streaming" ||
|
||||
props.template === "multiagent" ||
|
||||
props.template === "extractor"
|
||||
) {
|
||||
if (props.template !== "community" && props.template !== "llamapack") {
|
||||
await createBackendEnvFile(props.root, props);
|
||||
}
|
||||
|
||||
@@ -193,6 +231,7 @@ export const installTemplate = async (
|
||||
props.vectorDb,
|
||||
props.llamaCloudKey,
|
||||
props.useLlamaParse,
|
||||
props.useCase,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -203,7 +242,6 @@ export const installTemplate = async (
|
||||
} else {
|
||||
// this is a frontend for a full-stack app, create .env file with model information
|
||||
await createFrontendEnvFile(props.root, {
|
||||
customApiPath: props.customApiPath,
|
||||
vectorDb: props.vectorDb,
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import spawn from "cross-spawn";
|
||||
import { yellow } from "picocolors";
|
||||
import type { PackageManager } from "./get-pkg-manager";
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { blue, green } from "picocolors";
|
||||
@@ -143,6 +143,6 @@ export const installLlamapackProject = async ({
|
||||
await copyData({ root });
|
||||
await installLlamapackExample({ root, llamapack });
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
installPythonDependencies({ noRoot: true });
|
||||
installPythonDependencies();
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
|
||||
+1
-4
@@ -1,4 +1,3 @@
|
||||
import ciInfo from "ci-info";
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams } from ".";
|
||||
import { questionHandlers, toChoice } from "../../questions/utils";
|
||||
@@ -70,9 +69,7 @@ export async function askAnthropicQuestions({
|
||||
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) {
|
||||
if (askModels) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
@@ -1,4 +1,3 @@
|
||||
import ciInfo from "ci-info";
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams, ModelConfigQuestionsParams } from ".";
|
||||
import { questionHandlers } from "../../questions/utils";
|
||||
@@ -67,9 +66,7 @@ export async function askAzureQuestions({
|
||||
},
|
||||
};
|
||||
|
||||
// use default model values in CI or if user should not be asked
|
||||
const useDefaults = ciInfo.isCI || !askModels;
|
||||
if (!useDefaults) {
|
||||
if (askModels) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
@@ -1,4 +1,3 @@
|
||||
import ciInfo from "ci-info";
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams } from ".";
|
||||
import { questionHandlers, toChoice } from "../../questions/utils";
|
||||
@@ -54,9 +53,7 @@ export async function askGeminiQuestions({
|
||||
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) {
|
||||
if (askModels) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
@@ -1,4 +1,3 @@
|
||||
import ciInfo from "ci-info";
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams } from ".";
|
||||
import { questionHandlers, toChoice } from "../../questions/utils";
|
||||
@@ -110,9 +109,7 @@ export async function askGroqQuestions({
|
||||
config.apiKey = key || process.env.GROQ_API_KEY;
|
||||
}
|
||||
|
||||
// use default model values in CI or if user should not be asked
|
||||
const useDefaults = ciInfo.isCI || !askModels;
|
||||
if (!useDefaults) {
|
||||
if (askModels) {
|
||||
const modelChoices = await getAvailableModelChoicesGroq(config.apiKey!);
|
||||
|
||||
const { model } = await prompts(
|
||||
@@ -0,0 +1,68 @@
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams } from ".";
|
||||
import { questionHandlers, toChoice } from "../../questions/utils";
|
||||
|
||||
const MODELS = ["HuggingFaceH4/zephyr-7b-alpha"];
|
||||
type ModelData = {
|
||||
dimensions: number;
|
||||
};
|
||||
const EMBEDDING_MODELS: Record<string, ModelData> = {
|
||||
"BAAI/bge-small-en-v1.5": { dimensions: 384 },
|
||||
"BAAI/bge-base-en-v1.5": { dimensions: 768 },
|
||||
"BAAI/bge-large-en-v1.5": { dimensions: 1024 },
|
||||
"sentence-transformers/all-MiniLM-L6-v2": { dimensions: 384 },
|
||||
"sentence-transformers/all-mpnet-base-v2": { dimensions: 768 },
|
||||
"intfloat/multilingual-e5-large": { dimensions: 1024 },
|
||||
"mixedbread-ai/mxbai-embed-large-v1": { dimensions: 1024 },
|
||||
"nomic-ai/nomic-embed-text-v1.5": { 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 HuggingfaceQuestionsParams = {
|
||||
askModels: boolean;
|
||||
};
|
||||
|
||||
export async function askHuggingfaceQuestions({
|
||||
askModels,
|
||||
}: HuggingfaceQuestionsParams): Promise<ModelConfigParams> {
|
||||
const config: ModelConfigParams = {
|
||||
model: DEFAULT_MODEL,
|
||||
embeddingModel: DEFAULT_EMBEDDING_MODEL,
|
||||
dimensions: DEFAULT_DIMENSIONS,
|
||||
isConfigured(): boolean {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
if (askModels) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "model",
|
||||
message: "Which Hugging Face 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;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { askAnthropicQuestions } from "./anthropic";
|
||||
import { askAzureQuestions } from "./azure";
|
||||
import { askGeminiQuestions } from "./gemini";
|
||||
import { askGroqQuestions } from "./groq";
|
||||
import { askHuggingfaceQuestions } from "./huggingface";
|
||||
import { askLLMHubQuestions } from "./llmhub";
|
||||
import { askMistralQuestions } from "./mistral";
|
||||
import { askOllamaQuestions } from "./ollama";
|
||||
@@ -27,7 +28,7 @@ export async function askModelConfig({
|
||||
}: ModelConfigQuestionsParams): Promise<ModelConfig> {
|
||||
let modelProvider: ModelProvider = DEFAULT_MODEL_PROVIDER;
|
||||
if (askModels) {
|
||||
let choices = [
|
||||
const choices = [
|
||||
{ title: "OpenAI", value: "openai" },
|
||||
{ title: "Groq", value: "groq" },
|
||||
{ title: "Ollama", value: "ollama" },
|
||||
@@ -39,6 +40,7 @@ export async function askModelConfig({
|
||||
|
||||
if (framework === "fastapi") {
|
||||
choices.push({ title: "T-Systems", value: "t-systems" });
|
||||
choices.push({ title: "Huggingface", value: "huggingface" });
|
||||
}
|
||||
const { provider } = await prompts(
|
||||
{
|
||||
@@ -76,6 +78,9 @@ export async function askModelConfig({
|
||||
case "t-systems":
|
||||
modelConfig = await askLLMHubQuestions({ askModels });
|
||||
break;
|
||||
case "huggingface":
|
||||
modelConfig = await askHuggingfaceQuestions({ askModels });
|
||||
break;
|
||||
default:
|
||||
modelConfig = await askOpenAIQuestions({
|
||||
openAiKey,
|
||||
@@ -1,4 +1,3 @@
|
||||
import ciInfo from "ci-info";
|
||||
import got from "got";
|
||||
import ora from "ora";
|
||||
import { red } from "picocolors";
|
||||
@@ -80,9 +79,7 @@ export async function askLLMHubQuestions({
|
||||
config.apiKey = key || process.env.T_SYSTEMS_LLMHUB_API_KEY;
|
||||
}
|
||||
|
||||
// use default model values in CI or if user should not be asked
|
||||
const useDefaults = ciInfo.isCI || !askModels;
|
||||
if (!useDefaults) {
|
||||
if (askModels) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
@@ -1,4 +1,3 @@
|
||||
import ciInfo from "ci-info";
|
||||
import prompts from "prompts";
|
||||
import { ModelConfigParams } from ".";
|
||||
import { questionHandlers, toChoice } from "../../questions/utils";
|
||||
@@ -53,9 +52,7 @@ export async function askMistralQuestions({
|
||||
config.apiKey = key || process.env.MISTRAL_API_KEY;
|
||||
}
|
||||
|
||||
// use default model values in CI or if user should not be asked
|
||||
const useDefaults = ciInfo.isCI || !askModels;
|
||||
if (!useDefaults) {
|
||||
if (askModels) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
@@ -1,4 +1,3 @@
|
||||
import ciInfo from "ci-info";
|
||||
import ollama, { type ModelResponse } from "ollama";
|
||||
import { red } from "picocolors";
|
||||
import prompts from "prompts";
|
||||
@@ -34,9 +33,7 @@ export async function askOllamaQuestions({
|
||||
},
|
||||
};
|
||||
|
||||
// use default model values in CI or if user should not be asked
|
||||
const useDefaults = ciInfo.isCI || !askModels;
|
||||
if (!useDefaults) {
|
||||
if (askModels) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
@@ -1,9 +1,9 @@
|
||||
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 { isCI } from "../../questions";
|
||||
import { questionHandlers } from "../../questions/utils";
|
||||
|
||||
const OPENAI_API_URL = "https://api.openai.com/v1";
|
||||
@@ -31,7 +31,7 @@ export async function askOpenAIQuestions({
|
||||
},
|
||||
};
|
||||
|
||||
if (!config.apiKey) {
|
||||
if (!config.apiKey && !isCI) {
|
||||
const { key } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
@@ -54,9 +54,7 @@ export async function askOpenAIQuestions({
|
||||
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) {
|
||||
if (askModels) {
|
||||
const { model } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
@@ -3,15 +3,17 @@ import path from "path";
|
||||
import { cyan, red } from "picocolors";
|
||||
import { parse, stringify } from "smol-toml";
|
||||
import terminalLink from "terminal-link";
|
||||
import { isUvAvailable, tryUvSync } from "./uv";
|
||||
|
||||
import { isCI } from "ci-info";
|
||||
import { assetRelocator, copy } from "./copy";
|
||||
import { templatesDir } from "./dir";
|
||||
import { isPoetryAvailable, tryPoetryInstall } from "./poetry";
|
||||
import { Tool } from "./tools";
|
||||
import {
|
||||
InstallTemplateArgs,
|
||||
ModelConfig,
|
||||
TemplateDataSource,
|
||||
TemplateObservability,
|
||||
TemplateType,
|
||||
TemplateVectorDB,
|
||||
} from "./types";
|
||||
@@ -20,6 +22,7 @@ interface Dependency {
|
||||
name: string;
|
||||
version?: string;
|
||||
extras?: string[];
|
||||
constraints?: Record<string, string>;
|
||||
}
|
||||
|
||||
const getAdditionalDependencies = (
|
||||
@@ -28,6 +31,8 @@ const getAdditionalDependencies = (
|
||||
dataSources?: TemplateDataSource[],
|
||||
tools?: Tool[],
|
||||
templateType?: TemplateType,
|
||||
observability?: TemplateObservability,
|
||||
// eslint-disable-next-line max-params
|
||||
) => {
|
||||
const dependencies: Dependency[] = [];
|
||||
|
||||
@@ -36,67 +41,77 @@ const getAdditionalDependencies = (
|
||||
case "mongo": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-mongodb",
|
||||
version: "^0.3.1",
|
||||
version: ">=0.3.2,<0.4.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "pg": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-postgres",
|
||||
version: "^0.2.5",
|
||||
version: ">=0.3.2,<0.4.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "pinecone": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-pinecone",
|
||||
version: "^0.2.1",
|
||||
version: ">=0.4.1,<0.5.0",
|
||||
constraints: {
|
||||
python: ">=3.11,<3.13",
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "milvus": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-milvus",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "pymilvus",
|
||||
version: "2.4.4",
|
||||
version: ">=2.4.4,<3.0.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "astra": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-astra-db",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "qdrant": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-qdrant",
|
||||
version: "^0.3.0",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
constraints: {
|
||||
python: ">=3.11,<3.13",
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "chroma": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-chroma",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "onnxruntime",
|
||||
version: "<1.22.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "weaviate": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-weaviate",
|
||||
version: "^1.1.1",
|
||||
version: ">=1.2.3,<2.0.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "llamacloud":
|
||||
dependencies.push({
|
||||
name: "llama-index-indices-managed-llama-cloud",
|
||||
version: "^0.3.1",
|
||||
version: ">=0.6.3,<0.7.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -109,28 +124,28 @@ const getAdditionalDependencies = (
|
||||
case "file":
|
||||
dependencies.push({
|
||||
name: "docx2txt",
|
||||
version: "^0.8",
|
||||
version: ">=0.8,<0.9",
|
||||
});
|
||||
break;
|
||||
case "web":
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-web",
|
||||
version: "^0.2.2",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
break;
|
||||
case "db":
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-database",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "pymysql",
|
||||
version: "^1.1.0",
|
||||
version: ">=1.1.0,<2.0.0",
|
||||
extras: ["rsa"],
|
||||
});
|
||||
dependencies.push({
|
||||
name: "psycopg2-binary",
|
||||
version: "^2.9.9",
|
||||
version: ">=2.9.9,<3.0.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -149,135 +164,135 @@ const getAdditionalDependencies = (
|
||||
case "ollama":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-ollama",
|
||||
version: "0.3.0",
|
||||
version: ">=0.5.0,<0.6.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-ollama",
|
||||
version: "0.3.0",
|
||||
version: ">=0.6.0,<0.7.0",
|
||||
});
|
||||
break;
|
||||
case "openai":
|
||||
if (templateType !== "multiagent") {
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-openai",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.3.2,<0.4.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-openai",
|
||||
version: "^0.2.3",
|
||||
version: ">=0.3.1,<0.4.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-agent-openai",
|
||||
version: "^0.3.0",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "groq":
|
||||
// Fastembed==0.2.0 does not support python3.13 at the moment
|
||||
// Fixed the python version less than 3.13
|
||||
dependencies.push({
|
||||
name: "python",
|
||||
version: "^3.11,<3.13",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-groq",
|
||||
version: "0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-fastembed",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
break;
|
||||
case "anthropic":
|
||||
// Fastembed==0.2.0 does not support python3.13 at the moment
|
||||
// Fixed the python version less than 3.13
|
||||
dependencies.push({
|
||||
name: "python",
|
||||
version: "^3.11,<3.13",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-anthropic",
|
||||
version: "0.3.0",
|
||||
version: ">=0.6.0,<0.7.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-fastembed",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
break;
|
||||
case "gemini":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-gemini",
|
||||
version: "0.3.4",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-gemini",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
break;
|
||||
case "mistral":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-mistralai",
|
||||
version: "0.2.1",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-mistralai",
|
||||
version: "0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
break;
|
||||
case "azure-openai":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-azure-openai",
|
||||
version: "0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-azure-openai",
|
||||
version: "0.2.4",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
break;
|
||||
case "huggingface":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-huggingface",
|
||||
version: ">=0.5.0,<0.6.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-huggingface",
|
||||
version: ">=0.5.0,<0.6.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "optimum",
|
||||
version: ">=1.23.3,<2.0.0",
|
||||
extras: ["onnxruntime"],
|
||||
});
|
||||
break;
|
||||
case "t-systems":
|
||||
dependencies.push({
|
||||
name: "llama-index-agent-openai",
|
||||
version: "0.3.0",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-openai-like",
|
||||
version: "0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
const mergePoetryDependencies = (
|
||||
dependencies: Dependency[],
|
||||
existingDependencies: Record<string, Omit<Dependency, "name"> | string>,
|
||||
) => {
|
||||
for (const dependency of dependencies) {
|
||||
let value = existingDependencies[dependency.name] ?? {};
|
||||
|
||||
// default string value is equal to attribute "version"
|
||||
if (typeof value === "string") {
|
||||
value = { version: value };
|
||||
if (observability && observability !== "none") {
|
||||
if (observability === "traceloop") {
|
||||
dependencies.push({
|
||||
name: "traceloop-sdk",
|
||||
version: ">=0.15.11",
|
||||
});
|
||||
}
|
||||
|
||||
value.version = dependency.version ?? value.version;
|
||||
value.extras = dependency.extras ?? value.extras;
|
||||
|
||||
if (value.version === undefined) {
|
||||
throw new Error(
|
||||
`Dependency "${dependency.name}" is missing attribute "version"!`,
|
||||
);
|
||||
}
|
||||
|
||||
// Serialize separately only if extras are provided
|
||||
if (value.extras && value.extras.length > 0) {
|
||||
existingDependencies[dependency.name] = value;
|
||||
} else {
|
||||
// Otherwise, serialize just the version string
|
||||
existingDependencies[dependency.name] = value.version;
|
||||
if (observability === "llamatrace") {
|
||||
dependencies.push({
|
||||
name: "llama-index-callbacks-arize-phoenix",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If app template is llama-index-server and CI and SERVER_PACKAGE_PATH is set,
|
||||
// add @llamaindex/server to dependencies
|
||||
if (
|
||||
templateType === "llamaindexserver" &&
|
||||
isCI &&
|
||||
process.env.SERVER_PACKAGE_PATH
|
||||
) {
|
||||
dependencies.push({
|
||||
name: "llama-index-server",
|
||||
version: `@file://${process.env.SERVER_PACKAGE_PATH}`,
|
||||
});
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
const copyRouterCode = async (root: string, tools: Tool[]) => {
|
||||
@@ -302,19 +317,100 @@ export const addDependencies = async (
|
||||
// Parse toml file
|
||||
const file = path.join(projectDir, FILENAME);
|
||||
const fileContent = await fs.readFile(file, "utf8");
|
||||
const fileParsed = parse(fileContent);
|
||||
let fileParsed: any;
|
||||
try {
|
||||
fileParsed = parse(fileContent);
|
||||
} catch (parseError) {
|
||||
console.error(`Error parsing ${FILENAME}:`, parseError);
|
||||
throw new Error(
|
||||
`Failed to parse ${FILENAME}. Please ensure it's valid TOML.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Modify toml dependencies
|
||||
const tool = fileParsed.tool as any;
|
||||
const existingDependencies = tool.poetry.dependencies;
|
||||
mergePoetryDependencies(dependencies, existingDependencies);
|
||||
// Ensure [project] and [project.dependencies] exist
|
||||
if (!fileParsed.project) {
|
||||
fileParsed.project = {};
|
||||
}
|
||||
if (
|
||||
!fileParsed.project.dependencies ||
|
||||
!Array.isArray(fileParsed.project.dependencies)
|
||||
) {
|
||||
// If dependencies exist but aren't an array, log a warning or error.
|
||||
// For now, we'll overwrite it, assuming the intent is to use the standard array format.
|
||||
console.warn(
|
||||
`[project.dependencies] in ${FILENAME} is not an array. It will be overwritten.`,
|
||||
);
|
||||
fileParsed.project.dependencies = [];
|
||||
}
|
||||
|
||||
const existingDependencies: string[] = fileParsed.project.dependencies;
|
||||
const addedDeps: string[] = [];
|
||||
const updatedDeps: string[] = [];
|
||||
|
||||
// Add or update dependencies
|
||||
for (const newDep of dependencies) {
|
||||
let depString = newDep.name;
|
||||
if (newDep.extras && newDep.extras.length > 0) {
|
||||
depString += `[${newDep.extras.join(",")}]`;
|
||||
}
|
||||
if (newDep.version) {
|
||||
depString += newDep.version;
|
||||
}
|
||||
|
||||
let found = false;
|
||||
for (let i = 0; i < existingDependencies.length; i++) {
|
||||
const existingDepNameMatch =
|
||||
existingDependencies[i].match(/^([a-zA-Z0-9._-]+)/);
|
||||
if (
|
||||
existingDepNameMatch &&
|
||||
existingDepNameMatch[1].toLowerCase() === depString.toLowerCase()
|
||||
) {
|
||||
// Found existing dependency, update it
|
||||
if (existingDependencies[i] !== depString) {
|
||||
updatedDeps.push(`${existingDependencies[i]} -> ${depString}`);
|
||||
existingDependencies[i] = depString;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Add new dependency
|
||||
existingDependencies.push(depString);
|
||||
addedDeps.push(depString);
|
||||
}
|
||||
// Handle python version constraints separately (if any)
|
||||
if (newDep.constraints?.python) {
|
||||
if (
|
||||
!fileParsed.project["requires-python"] ||
|
||||
fileParsed.project["requires-python"] !== newDep.constraints.python
|
||||
) {
|
||||
// This simple overwrite might not be ideal; merging constraints is complex.
|
||||
// For now, let's just set it if the new dependency has one.
|
||||
console.log(
|
||||
`Setting requires-python = "${newDep.constraints.python}" from dependency ${newDep.name}`,
|
||||
);
|
||||
fileParsed.project["requires-python"] = newDep.constraints.python;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write toml file
|
||||
const newFileContent = stringify(fileParsed);
|
||||
await fs.writeFile(file, newFileContent);
|
||||
|
||||
const dependenciesString = dependencies.map((d) => d.name).join(", ");
|
||||
console.log(`\nAdded ${dependenciesString} to ${cyan(FILENAME)}\n`);
|
||||
if (addedDeps.length > 0) {
|
||||
console.log(`\nAdded dependencies to ${cyan(FILENAME)}:`);
|
||||
addedDeps.forEach((dep) => console.log(` ${dep}`));
|
||||
}
|
||||
if (updatedDeps.length > 0) {
|
||||
console.log(`\nUpdated dependencies in ${cyan(FILENAME)}:`);
|
||||
updatedDeps.forEach((dep) => console.log(` ${dep}`));
|
||||
}
|
||||
if (addedDeps.length > 0 || updatedDeps.length > 0) {
|
||||
console.log(""); // Newline for spacing
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`Error while updating dependencies for Poetry project file ${FILENAME}\n`,
|
||||
@@ -323,18 +419,16 @@ export const addDependencies = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const installPythonDependencies = (
|
||||
{ noRoot }: { noRoot: boolean } = { noRoot: false },
|
||||
) => {
|
||||
if (isPoetryAvailable()) {
|
||||
export const installPythonDependencies = () => {
|
||||
if (isUvAvailable()) {
|
||||
console.log(
|
||||
`Installing python dependencies using poetry. This may take a while...`,
|
||||
`Installing Python dependencies using uv. This may take a while...`,
|
||||
);
|
||||
const installSuccessful = tryPoetryInstall(noRoot);
|
||||
const installSuccessful = tryUvSync();
|
||||
if (!installSuccessful) {
|
||||
console.error(
|
||||
red(
|
||||
"Installing dependencies using poetry failed. Please check error log above and try running create-llama again.",
|
||||
"Installing dependencies using uv failed. Please check the error log above and ensure uv is installed correctly.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -342,51 +436,34 @@ export const installPythonDependencies = (
|
||||
} else {
|
||||
console.error(
|
||||
red(
|
||||
`Poetry is not available in the current environment. Please check ${terminalLink(
|
||||
"Poetry Installation",
|
||||
`https://python-poetry.org/docs/#installation`,
|
||||
)} to install poetry first, then run create-llama again.`,
|
||||
`uv is not available in the current environment. Please check ${terminalLink(
|
||||
"uv Installation",
|
||||
`https://github.com/astral-sh/uv#installation`,
|
||||
)} to install uv first, then run create-llama again.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
export const installPythonTemplate = async ({
|
||||
const installLegacyPythonTemplate = async ({
|
||||
root,
|
||||
template,
|
||||
framework,
|
||||
vectorDb,
|
||||
dataSources,
|
||||
tools,
|
||||
postInstallAction,
|
||||
useCase,
|
||||
observability,
|
||||
modelConfig,
|
||||
}: Pick<
|
||||
InstallTemplateArgs,
|
||||
| "root"
|
||||
| "framework"
|
||||
| "template"
|
||||
| "vectorDb"
|
||||
| "dataSources"
|
||||
| "tools"
|
||||
| "postInstallAction"
|
||||
| "useCase"
|
||||
| "observability"
|
||||
| "modelConfig"
|
||||
>) => {
|
||||
console.log("\nInitializing Python project with template:", template, "\n");
|
||||
let templatePath;
|
||||
if (template === "extractor") {
|
||||
templatePath = path.join(templatesDir, "types", "extractor", framework);
|
||||
} else {
|
||||
templatePath = path.join(templatesDir, "types", "streaming", framework);
|
||||
}
|
||||
await copy("**", root, {
|
||||
parents: true,
|
||||
cwd: templatePath,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
const enginePath = path.join(root, "app", "engine");
|
||||
|
||||
@@ -447,40 +524,36 @@ export const installPythonTemplate = async ({
|
||||
await copyRouterCode(root, tools ?? []);
|
||||
}
|
||||
|
||||
// Copy multiagents overrides
|
||||
if (template === "multiagent") {
|
||||
// Copy multi-agent code
|
||||
await copy("**", path.join(root), {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "multiagent", "python"),
|
||||
rename: assetRelocator,
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Adding additional dependencies");
|
||||
if (template === "multiagent" || template === "reflex") {
|
||||
if (useCase) {
|
||||
const sourcePath =
|
||||
template === "multiagent"
|
||||
? path.join(compPath, "agents", "python", useCase)
|
||||
: path.join(compPath, "reflex", useCase);
|
||||
|
||||
const addOnDependencies = getAdditionalDependencies(
|
||||
modelConfig,
|
||||
vectorDb,
|
||||
dataSources,
|
||||
tools,
|
||||
template,
|
||||
);
|
||||
await copy("**", path.join(root), {
|
||||
parents: true,
|
||||
cwd: sourcePath,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
red(
|
||||
`There is no use case selected for ${template} template. Please pick a use case to use via --use-case flag.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (observability && observability !== "none") {
|
||||
if (observability === "traceloop") {
|
||||
addOnDependencies.push({
|
||||
name: "traceloop-sdk",
|
||||
version: "^0.15.11",
|
||||
});
|
||||
}
|
||||
|
||||
if (observability === "llamatrace") {
|
||||
addOnDependencies.push({
|
||||
name: "llama-index-callbacks-arize-phoenix",
|
||||
version: "^0.2.1",
|
||||
});
|
||||
}
|
||||
|
||||
const templateObservabilityPath = path.join(
|
||||
templatesDir,
|
||||
"components",
|
||||
@@ -492,15 +565,144 @@ export const installPythonTemplate = async ({
|
||||
cwd: templateObservabilityPath,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const installLlamaIndexServerTemplate = async ({
|
||||
root,
|
||||
useCase,
|
||||
useLlamaParse,
|
||||
}: Pick<InstallTemplateArgs, "root" | "useCase" | "useLlamaParse">) => {
|
||||
if (!useCase) {
|
||||
console.log(
|
||||
red(
|
||||
`There is no use case selected. Please pick a use case to use via --use-case flag.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await copy("*.py", path.join(root, "app"), {
|
||||
parents: true,
|
||||
cwd: path.join(templatesDir, "components", "use-cases", "python", useCase),
|
||||
});
|
||||
|
||||
// Copy custom UI component code
|
||||
await copy(`*`, path.join(root, "components"), {
|
||||
parents: true,
|
||||
cwd: path.join(templatesDir, "components", "ui", "use-cases", useCase),
|
||||
});
|
||||
|
||||
// Copy layout components to layout folder in root
|
||||
await copy("*", path.join(root, "layout"), {
|
||||
parents: true,
|
||||
cwd: path.join(templatesDir, "components", "ui", "layout"),
|
||||
});
|
||||
|
||||
if (useLlamaParse) {
|
||||
await copy("index.py", path.join(root, "app"), {
|
||||
parents: true,
|
||||
cwd: path.join(
|
||||
templatesDir,
|
||||
"components",
|
||||
"vectordbs",
|
||||
"llamaindexserver",
|
||||
"llamacloud",
|
||||
"python",
|
||||
),
|
||||
});
|
||||
// TODO: Consider moving generate.py to app folder.
|
||||
await copy("generate.py", path.join(root), {
|
||||
parents: true,
|
||||
cwd: path.join(
|
||||
templatesDir,
|
||||
"components",
|
||||
"vectordbs",
|
||||
"llamaindexserver",
|
||||
"llamacloud",
|
||||
"python",
|
||||
),
|
||||
});
|
||||
}
|
||||
// Copy README.md
|
||||
await copy("README-template.md", path.join(root), {
|
||||
parents: true,
|
||||
cwd: path.join(templatesDir, "components", "use-cases", "python", useCase),
|
||||
rename: assetRelocator,
|
||||
});
|
||||
};
|
||||
|
||||
export const installPythonTemplate = async ({
|
||||
appName,
|
||||
root,
|
||||
template,
|
||||
framework,
|
||||
vectorDb,
|
||||
postInstallAction,
|
||||
modelConfig,
|
||||
dataSources,
|
||||
tools,
|
||||
useLlamaParse,
|
||||
useCase,
|
||||
observability,
|
||||
}: Pick<
|
||||
InstallTemplateArgs,
|
||||
| "appName"
|
||||
| "root"
|
||||
| "template"
|
||||
| "framework"
|
||||
| "vectorDb"
|
||||
| "postInstallAction"
|
||||
| "modelConfig"
|
||||
| "dataSources"
|
||||
| "tools"
|
||||
| "useLlamaParse"
|
||||
| "useCase"
|
||||
| "observability"
|
||||
>) => {
|
||||
console.log("\nInitializing Python project with template:", template, "\n");
|
||||
let templatePath;
|
||||
if (template === "reflex") {
|
||||
templatePath = path.join(templatesDir, "types", "reflex");
|
||||
} else {
|
||||
templatePath = path.join(templatesDir, "types", template, framework);
|
||||
}
|
||||
await copy("**", root, {
|
||||
parents: true,
|
||||
cwd: templatePath,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
|
||||
if (template === "llamaindexserver") {
|
||||
await installLlamaIndexServerTemplate({
|
||||
root,
|
||||
useCase,
|
||||
useLlamaParse,
|
||||
});
|
||||
} else {
|
||||
await installLegacyPythonTemplate({
|
||||
root,
|
||||
template,
|
||||
vectorDb,
|
||||
dataSources,
|
||||
tools,
|
||||
useCase,
|
||||
observability,
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Adding additional dependencies");
|
||||
const addOnDependencies = getAdditionalDependencies(
|
||||
modelConfig,
|
||||
vectorDb,
|
||||
dataSources,
|
||||
tools,
|
||||
template,
|
||||
observability,
|
||||
);
|
||||
|
||||
await addDependencies(root, addOnDependencies);
|
||||
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
installPythonDependencies();
|
||||
}
|
||||
|
||||
// Copy deployment files for python
|
||||
await copy("**", root, {
|
||||
cwd: path.join(compPath, "deployments", "python"),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import { SpawnOptions, spawn } from "child_process";
|
||||
import { TemplateFramework, TemplateType } from "./types";
|
||||
|
||||
const createProcess = (
|
||||
command: string,
|
||||
args: string[],
|
||||
options: SpawnOptions,
|
||||
): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
spawn(command, args, {
|
||||
...options,
|
||||
shell: true,
|
||||
})
|
||||
.on("exit", function (code) {
|
||||
if (code !== 0) {
|
||||
console.log(`Child process exited with code=${code}`);
|
||||
reject(code);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.on("error", function (err) {
|
||||
console.log("Error when running child process: ", err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export function runReflexApp(appPath: string, port: number) {
|
||||
const commandArgs = [
|
||||
"run",
|
||||
"reflex",
|
||||
"run",
|
||||
"--frontend-port",
|
||||
port.toString(),
|
||||
];
|
||||
return createProcess("uv", commandArgs, {
|
||||
stdio: "inherit",
|
||||
cwd: appPath,
|
||||
});
|
||||
}
|
||||
|
||||
export function runFastAPIApp(
|
||||
appPath: string,
|
||||
port: number,
|
||||
template: TemplateType,
|
||||
) {
|
||||
let commandArgs: string[];
|
||||
if (template === "streaming") {
|
||||
commandArgs = ["run", "dev"];
|
||||
} else {
|
||||
commandArgs = ["run", "fastapi", "dev", "--port", `${port}`];
|
||||
}
|
||||
return createProcess("uv", commandArgs, {
|
||||
stdio: "inherit",
|
||||
cwd: appPath,
|
||||
env: { ...process.env, APP_PORT: `${port}` },
|
||||
});
|
||||
}
|
||||
|
||||
export function runTSApp(appPath: string, port: number) {
|
||||
return createProcess("npm", ["run", "dev"], {
|
||||
stdio: "inherit",
|
||||
cwd: appPath,
|
||||
env: { ...process.env, PORT: `${port}` },
|
||||
});
|
||||
}
|
||||
|
||||
export async function runApp(
|
||||
appPath: string,
|
||||
template: TemplateType,
|
||||
framework: TemplateFramework,
|
||||
port?: number,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Start the app
|
||||
const defaultPort =
|
||||
framework === "nextjs" || template === "reflex" ? 3000 : 8000;
|
||||
|
||||
const appRunner =
|
||||
template === "reflex"
|
||||
? runReflexApp
|
||||
: framework === "fastapi"
|
||||
? runFastAPIApp
|
||||
: runTSApp;
|
||||
await appRunner(appPath, port || defaultPort, template);
|
||||
} catch (error) {
|
||||
console.error("Failed to run app:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export const supportedTools: Tool[] = [
|
||||
dependencies: [
|
||||
{
|
||||
name: "llama-index-tools-google",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi"],
|
||||
@@ -62,17 +62,16 @@ export const supportedTools: Tool[] = [
|
||||
dependencies: [
|
||||
{
|
||||
name: "duckduckgo-search",
|
||||
version: "6.1.7",
|
||||
version: ">=6.3.5,<7.0.0",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi", "nextjs", "express"],
|
||||
supportedFrameworks: ["fastapi"], // TODO: Re-enable this tool once the duck-duck-scrape TypeScript library works again
|
||||
type: ToolType.LOCAL,
|
||||
envVars: [
|
||||
{
|
||||
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
|
||||
description: "System prompt for DuckDuckGo search tool.",
|
||||
value: `You are a DuckDuckGo search agent.
|
||||
You can use the duckduckgo search tool to get information from the web to answer user questions.
|
||||
value: `You have access to the duckduckgo search tool. Use it to get information from the web to answer user questions.
|
||||
For better results, you can specify the region parameter to get results from a specific region but it's optional.`,
|
||||
},
|
||||
],
|
||||
@@ -83,18 +82,11 @@ For better results, you can specify the region parameter to get results from a s
|
||||
dependencies: [
|
||||
{
|
||||
name: "llama-index-tools-wikipedia",
|
||||
version: "^0.2.0",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
},
|
||||
],
|
||||
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",
|
||||
@@ -102,13 +94,6 @@ For better results, you can specify the region parameter to get results from a s
|
||||
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: "Document generator",
|
||||
@@ -117,11 +102,11 @@ For better results, you can specify the region parameter to get results from a s
|
||||
dependencies: [
|
||||
{
|
||||
name: "xhtml2pdf",
|
||||
version: "^0.2.14",
|
||||
version: ">=0.2.14,<0.3.0",
|
||||
},
|
||||
{
|
||||
name: "markdown",
|
||||
version: "^3.7",
|
||||
version: ">=3.7.0,<4.0.0",
|
||||
},
|
||||
],
|
||||
type: ToolType.LOCAL,
|
||||
@@ -139,7 +124,7 @@ For better results, you can specify the region parameter to get results from a s
|
||||
dependencies: [
|
||||
{
|
||||
name: "e2b_code_interpreter",
|
||||
version: "0.0.10",
|
||||
version: ">=1.1.1,<1.2.0",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi", "express", "nextjs"],
|
||||
@@ -170,7 +155,7 @@ For better results, you can specify the region parameter to get results from a s
|
||||
dependencies: [
|
||||
{
|
||||
name: "e2b_code_interpreter",
|
||||
version: "^0.0.11b38",
|
||||
version: ">=1.1.1,<1.2.0",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi", "express", "nextjs"],
|
||||
@@ -199,7 +184,7 @@ For better results, you can specify the region parameter to get results from a s
|
||||
},
|
||||
{
|
||||
name: "jsonschema",
|
||||
version: "^4.22.0",
|
||||
version: ">=4.22.0,<5.0.0",
|
||||
},
|
||||
{
|
||||
name: "llama-index-tools-requests",
|
||||
@@ -211,14 +196,6 @@ For better results, you can specify the region parameter to get results from a s
|
||||
},
|
||||
supportedFrameworks: ["fastapi", "express", "nextjs"],
|
||||
type: ToolType.LOCAL,
|
||||
envVars: [
|
||||
{
|
||||
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
|
||||
description: "System prompt for openapi action tool.",
|
||||
value:
|
||||
"You are an OpenAPI action agent. You help users to make requests to the provided OpenAPI schema.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
display: "Image Generator",
|
||||
@@ -231,11 +208,6 @@ For better results, you can specify the region parameter to get results from a s
|
||||
description:
|
||||
"STABILITY_API_KEY key is required to run image generator. Get it here: https://platform.stability.ai/account/keys",
|
||||
},
|
||||
{
|
||||
name: TOOL_SYSTEM_PROMPT_ENV_VAR,
|
||||
description: "System prompt for image generator tool.",
|
||||
value: `You are an image generator agent. You help users to generate images using the Stability API.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -267,6 +239,22 @@ For better results, you can specify the region parameter to get results from a s
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
display: "Form Filling",
|
||||
name: "form_filling",
|
||||
supportedFrameworks: ["fastapi"],
|
||||
type: ToolType.LOCAL,
|
||||
dependencies: [
|
||||
{
|
||||
name: "pandas",
|
||||
version: ">=2.2.3,<3.0.0",
|
||||
},
|
||||
{
|
||||
name: "tabulate",
|
||||
version: ">=0.9.0,<1.0.0",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const getTool = (toolName: string): Tool | undefined => {
|
||||
@@ -337,9 +325,16 @@ export const writeToolsConfig = async (
|
||||
yaml.stringify(configContent),
|
||||
);
|
||||
} else {
|
||||
// For Typescript, we treat llamahub tools as local tools
|
||||
const tsConfigContent = {
|
||||
local: {
|
||||
...configContent.local,
|
||||
...configContent.llamahub,
|
||||
},
|
||||
};
|
||||
await fs.writeFile(
|
||||
path.join(configPath, "tools.json"),
|
||||
JSON.stringify(configContent, null, 2),
|
||||
JSON.stringify(tsConfigContent, null, 2),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -9,6 +9,7 @@ export type ModelProvider =
|
||||
| "gemini"
|
||||
| "mistral"
|
||||
| "azure-openai"
|
||||
| "huggingface"
|
||||
| "t-systems";
|
||||
export type ModelConfig = {
|
||||
provider: ModelProvider;
|
||||
@@ -19,11 +20,12 @@ export type ModelConfig = {
|
||||
isConfigured(): boolean;
|
||||
};
|
||||
export type TemplateType =
|
||||
| "extractor"
|
||||
| "streaming"
|
||||
| "community"
|
||||
| "llamapack"
|
||||
| "multiagent";
|
||||
| "multiagent"
|
||||
| "reflex"
|
||||
| "llamaindexserver";
|
||||
export type TemplateFramework = "nextjs" | "express" | "fastapi";
|
||||
export type TemplateUI = "html" | "shadcn";
|
||||
export type TemplateVectorDB =
|
||||
@@ -48,10 +50,26 @@ export type TemplateDataSource = {
|
||||
};
|
||||
export type TemplateDataSourceType = "file" | "web" | "db";
|
||||
export type TemplateObservability = "none" | "traceloop" | "llamatrace";
|
||||
export type TemplateUseCase =
|
||||
| "financial_report"
|
||||
| "blog"
|
||||
| "deep_research"
|
||||
| "form_filling"
|
||||
| "extractor"
|
||||
| "contract_review"
|
||||
| "agentic_rag"
|
||||
| "code_generator"
|
||||
| "document_generator";
|
||||
// Config for both file and folder
|
||||
export type FileSourceConfig = {
|
||||
path: string;
|
||||
};
|
||||
export type FileSourceConfig =
|
||||
| {
|
||||
path: string;
|
||||
filename?: string;
|
||||
}
|
||||
| {
|
||||
url: URL;
|
||||
filename?: string;
|
||||
};
|
||||
export type WebSourceConfig = {
|
||||
baseUrl?: string;
|
||||
prefix?: string;
|
||||
@@ -83,15 +101,15 @@ export interface InstallTemplateArgs {
|
||||
framework: TemplateFramework;
|
||||
ui: TemplateUI;
|
||||
dataSources: TemplateDataSource[];
|
||||
customApiPath?: string;
|
||||
modelConfig: ModelConfig;
|
||||
llamaCloudKey?: string;
|
||||
useLlamaParse?: boolean;
|
||||
communityProjectConfig?: CommunityProjectConfig;
|
||||
llamapack?: string;
|
||||
vectorDb?: TemplateVectorDB;
|
||||
externalPort?: number;
|
||||
port?: number;
|
||||
postInstallAction?: TemplatePostInstallAction;
|
||||
tools?: Tool[];
|
||||
observability?: TemplateObservability;
|
||||
useCase?: TemplateUseCase;
|
||||
}
|
||||
@@ -1,47 +1,114 @@
|
||||
import fs from "fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { bold, cyan, yellow } from "picocolors";
|
||||
import { bold, cyan, red, yellow } from "picocolors";
|
||||
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";
|
||||
import { InstallTemplateArgs, ModelProvider, TemplateVectorDB } from "./types";
|
||||
|
||||
/**
|
||||
* Install a LlamaIndex internal template to a given `root` directory.
|
||||
*/
|
||||
export const installTSTemplate = async ({
|
||||
appName,
|
||||
const installLlamaIndexServerTemplate = async ({
|
||||
root,
|
||||
useCase,
|
||||
vectorDb,
|
||||
}: Pick<InstallTemplateArgs, "root" | "useCase" | "vectorDb">) => {
|
||||
if (!useCase) {
|
||||
console.log(
|
||||
red(
|
||||
`There is no use case selected. Please pick a use case to use via --use-case flag.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!vectorDb) {
|
||||
console.log(
|
||||
red(
|
||||
`There is no vector db selected. Please pick a vector db to use via --vector-db flag.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await copy("**", path.join(root), {
|
||||
cwd: path.join(
|
||||
templatesDir,
|
||||
"components",
|
||||
"use-cases",
|
||||
"typescript",
|
||||
useCase,
|
||||
),
|
||||
rename: assetRelocator,
|
||||
});
|
||||
|
||||
// copy workflow UI components to components folder in root
|
||||
await copy("*", path.join(root, "components"), {
|
||||
parents: true,
|
||||
cwd: path.join(templatesDir, "components", "ui", "use-cases", useCase),
|
||||
});
|
||||
|
||||
// copy layout components to layout folder in root
|
||||
await copy("*", path.join(root, "layout"), {
|
||||
parents: true,
|
||||
cwd: path.join(templatesDir, "components", "ui", "layout"),
|
||||
});
|
||||
|
||||
// Override generate.ts if workflow use case doesn't use custom UI
|
||||
if (vectorDb === "llamacloud") {
|
||||
await copy("generate.ts", path.join(root, "src"), {
|
||||
parents: true,
|
||||
cwd: path.join(
|
||||
templatesDir,
|
||||
"components",
|
||||
"vectordbs",
|
||||
"llamaindexserver",
|
||||
"llamacloud",
|
||||
"typescript",
|
||||
),
|
||||
});
|
||||
|
||||
await copy("index.ts", path.join(root, "src", "app"), {
|
||||
parents: true,
|
||||
cwd: path.join(
|
||||
templatesDir,
|
||||
"components",
|
||||
"vectordbs",
|
||||
"llamaindexserver",
|
||||
"llamacloud",
|
||||
"typescript",
|
||||
),
|
||||
rename: () => "data.ts",
|
||||
});
|
||||
}
|
||||
|
||||
// Simplify use case code
|
||||
if (useCase === "code_generator" || useCase === "document_generator") {
|
||||
// Artifact use case doesn't use index.
|
||||
// We don't need data.ts, generate.ts
|
||||
await fs.rm(path.join(root, "src", "app", "data.ts"));
|
||||
// TODO: Remove generate index in generate.ts and package.json if possible
|
||||
}
|
||||
};
|
||||
|
||||
const installLegacyTSTemplate = async ({
|
||||
root,
|
||||
packageManager,
|
||||
isOnline,
|
||||
template,
|
||||
backend,
|
||||
framework,
|
||||
ui,
|
||||
vectorDb,
|
||||
postInstallAction,
|
||||
backend,
|
||||
observability,
|
||||
tools,
|
||||
dataSources,
|
||||
useLlamaParse,
|
||||
}: InstallTemplateArgs & { backend: boolean }) => {
|
||||
console.log(bold(`Using ${packageManager}.`));
|
||||
|
||||
/**
|
||||
* Copy the template files to the target directory.
|
||||
*/
|
||||
console.log("\nInitializing project with template:", template, "\n");
|
||||
const templatePath = path.join(templatesDir, "types", "streaming", framework);
|
||||
const copySource = ["**"];
|
||||
|
||||
await copy(copySource, root, {
|
||||
parents: true,
|
||||
cwd: templatePath,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
|
||||
useCase,
|
||||
modelConfig,
|
||||
relativeEngineDestPath,
|
||||
}: InstallTemplateArgs & {
|
||||
backend: boolean;
|
||||
relativeEngineDestPath: string;
|
||||
}) => {
|
||||
/**
|
||||
* If next.js is used, update its configuration if necessary
|
||||
*/
|
||||
@@ -57,11 +124,9 @@ export const installTSTemplate = async ({
|
||||
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",
|
||||
);
|
||||
nextConfigJson.serverExternalPackages =
|
||||
nextConfigJson.serverExternalPackages ?? [];
|
||||
nextConfigJson.serverExternalPackages.push("@zilliz/milvus2-sdk-node");
|
||||
}
|
||||
}
|
||||
await fs.writeFile(
|
||||
@@ -98,10 +163,6 @@ export const installTSTemplate = async ({
|
||||
}
|
||||
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
const relativeEngineDestPath =
|
||||
framework === "nextjs"
|
||||
? path.join("app", "api", "chat")
|
||||
: path.join("src", "controllers");
|
||||
const enginePath = path.join(root, relativeEngineDestPath, "engine");
|
||||
|
||||
// copy llamaindex code for TS templates
|
||||
@@ -132,6 +193,34 @@ export const installTSTemplate = async ({
|
||||
cwd: path.join(multiagentPath, "workflow"),
|
||||
});
|
||||
|
||||
// Copy use case code for multiagent template
|
||||
if (useCase) {
|
||||
console.log("\nCopying use case:", useCase, "\n");
|
||||
const useCasePath = path.join(compPath, "agents", "typescript", useCase);
|
||||
const useCaseCodePath = path.join(useCasePath, "workflow");
|
||||
|
||||
// Copy use case codes
|
||||
await copy("**", path.join(root, relativeEngineDestPath, "workflow"), {
|
||||
parents: true,
|
||||
cwd: useCaseCodePath,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
|
||||
// Copy use case files to project root
|
||||
await copy("*.*", path.join(root), {
|
||||
parents: true,
|
||||
cwd: useCasePath,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
red(
|
||||
`There is no use case selected for ${template} template. Please pick a use case to use via --use-case flag.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (framework === "nextjs") {
|
||||
// patch route.ts file
|
||||
await copy("**", path.join(root, relativeEngineDestPath), {
|
||||
@@ -154,6 +243,12 @@ export const installTSTemplate = async ({
|
||||
cwd: path.join(compPath, "loaders", "typescript", loaderFolder),
|
||||
});
|
||||
|
||||
// copy provider settings
|
||||
await copy("**", enginePath, {
|
||||
parents: true,
|
||||
cwd: path.join(compPath, "providers", "typescript", modelConfig.provider),
|
||||
});
|
||||
|
||||
// Select and copy engine code based on data sources and tools
|
||||
let engine;
|
||||
tools = tools ?? [];
|
||||
@@ -202,6 +297,75 @@ export const installTSTemplate = async ({
|
||||
await fs.rm(path.join(root, "app", "api"), { recursive: true });
|
||||
await fs.rm(path.join(root, "config"), { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Install a LlamaIndex internal template to a given `root` directory.
|
||||
*/
|
||||
export const installTSTemplate = async ({
|
||||
appName,
|
||||
root,
|
||||
packageManager,
|
||||
isOnline,
|
||||
template,
|
||||
framework,
|
||||
ui,
|
||||
vectorDb,
|
||||
postInstallAction,
|
||||
backend,
|
||||
observability,
|
||||
tools,
|
||||
dataSources,
|
||||
useLlamaParse,
|
||||
useCase,
|
||||
modelConfig,
|
||||
}: InstallTemplateArgs & { backend: boolean }) => {
|
||||
console.log(bold(`Using ${packageManager}.`));
|
||||
|
||||
/**
|
||||
* Copy the template files to the target directory.
|
||||
*/
|
||||
console.log("\nInitializing project with template:", template, "\n");
|
||||
const templatePath = path.join(templatesDir, "types", template, framework);
|
||||
const copySource = ["**"];
|
||||
|
||||
await copy(copySource, root, {
|
||||
parents: true,
|
||||
cwd: templatePath,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
|
||||
const relativeEngineDestPath =
|
||||
framework === "nextjs"
|
||||
? path.join("app", "api", "chat")
|
||||
: path.join("src", "controllers");
|
||||
|
||||
if (template === "llamaindexserver") {
|
||||
await installLlamaIndexServerTemplate({
|
||||
root,
|
||||
useCase,
|
||||
vectorDb,
|
||||
});
|
||||
} else {
|
||||
await installLegacyTSTemplate({
|
||||
appName,
|
||||
root,
|
||||
packageManager,
|
||||
isOnline,
|
||||
template,
|
||||
backend,
|
||||
framework,
|
||||
ui,
|
||||
vectorDb,
|
||||
observability,
|
||||
tools,
|
||||
dataSources,
|
||||
useLlamaParse,
|
||||
useCase,
|
||||
modelConfig,
|
||||
relativeEngineDestPath,
|
||||
});
|
||||
}
|
||||
|
||||
const packageJson = await updatePackageJson({
|
||||
root,
|
||||
@@ -212,16 +376,79 @@ export const installTSTemplate = async ({
|
||||
ui,
|
||||
observability,
|
||||
vectorDb,
|
||||
backend,
|
||||
modelConfig,
|
||||
template,
|
||||
});
|
||||
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
if (
|
||||
backend &&
|
||||
(postInstallAction === "runApp" || postInstallAction === "dependencies")
|
||||
) {
|
||||
await installTSDependencies(packageJson, packageManager, isOnline);
|
||||
}
|
||||
};
|
||||
|
||||
// Copy deployment files for typescript
|
||||
await copy("**", root, {
|
||||
cwd: path.join(compPath, "deployments", "typescript"),
|
||||
});
|
||||
const providerDependencies: {
|
||||
[key in ModelProvider]?: Record<string, string>;
|
||||
} = {
|
||||
openai: {
|
||||
"@llamaindex/openai": "~0.4.0",
|
||||
},
|
||||
gemini: {
|
||||
"@llamaindex/google": "^0.2.0",
|
||||
},
|
||||
ollama: {
|
||||
"@llamaindex/ollama": "^0.1.0",
|
||||
},
|
||||
mistral: {
|
||||
"@llamaindex/mistral": "^0.2.0",
|
||||
},
|
||||
"azure-openai": {
|
||||
"@llamaindex/openai": "^0.2.0",
|
||||
},
|
||||
groq: {
|
||||
"@llamaindex/groq": "^0.0.61",
|
||||
"@llamaindex/huggingface": "^0.1.0", // groq uses huggingface as default embedding model
|
||||
},
|
||||
anthropic: {
|
||||
"@llamaindex/anthropic": "^0.3.0",
|
||||
"@llamaindex/huggingface": "^0.1.0", // anthropic uses huggingface as default embedding model
|
||||
},
|
||||
};
|
||||
|
||||
const vectorDbDependencies: Record<TemplateVectorDB, Record<string, string>> = {
|
||||
astra: {
|
||||
"@llamaindex/astra": "^0.0.5",
|
||||
},
|
||||
chroma: {
|
||||
"@llamaindex/chroma": "^0.0.5",
|
||||
},
|
||||
llamacloud: {},
|
||||
milvus: {
|
||||
"@zilliz/milvus2-sdk-node": "^2.4.6",
|
||||
"@llamaindex/milvus": "^0.1.0",
|
||||
},
|
||||
mongo: {
|
||||
mongodb: "6.7.0",
|
||||
"@llamaindex/mongodb": "^0.0.5",
|
||||
},
|
||||
none: {},
|
||||
pg: {
|
||||
pg: "^8.12.0",
|
||||
pgvector: "^0.2.0",
|
||||
"@llamaindex/postgres": "^0.0.33",
|
||||
},
|
||||
pinecone: {
|
||||
"@llamaindex/pinecone": "^0.0.5",
|
||||
},
|
||||
qdrant: {
|
||||
"@qdrant/js-client-rest": "^1.11.0",
|
||||
"@llamaindex/qdrant": "^0.1.0",
|
||||
},
|
||||
weaviate: {
|
||||
"@llamaindex/weaviate": "^0.0.5",
|
||||
},
|
||||
};
|
||||
|
||||
async function updatePackageJson({
|
||||
@@ -233,6 +460,9 @@ async function updatePackageJson({
|
||||
ui,
|
||||
observability,
|
||||
vectorDb,
|
||||
backend,
|
||||
modelConfig,
|
||||
template,
|
||||
}: Pick<
|
||||
InstallTemplateArgs,
|
||||
| "root"
|
||||
@@ -242,8 +472,11 @@ async function updatePackageJson({
|
||||
| "ui"
|
||||
| "observability"
|
||||
| "vectorDb"
|
||||
| "modelConfig"
|
||||
| "template"
|
||||
> & {
|
||||
relativeEngineDestPath: string;
|
||||
backend: boolean;
|
||||
}): Promise<any> {
|
||||
const packageJsonFile = path.join(root, "package.json");
|
||||
const packageJson: any = JSON.parse(
|
||||
@@ -252,7 +485,7 @@ async function updatePackageJson({
|
||||
packageJson.name = appName;
|
||||
packageJson.version = "0.1.0";
|
||||
|
||||
if (relativeEngineDestPath) {
|
||||
if (relativeEngineDestPath && template !== "llamaindexserver") {
|
||||
// TODO: move script to {root}/scripts for all frameworks
|
||||
// add generate script if using context engine
|
||||
packageJson.scripts = {
|
||||
@@ -279,41 +512,29 @@ async function updatePackageJson({
|
||||
"remark-gfm": undefined,
|
||||
"remark-math": undefined,
|
||||
"react-markdown": undefined,
|
||||
"react-syntax-highlighter": undefined,
|
||||
};
|
||||
|
||||
packageJson.devDependencies = {
|
||||
...packageJson.devDependencies,
|
||||
"@types/react-syntax-highlighter": undefined,
|
||||
"highlight.js": undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (vectorDb === "pg") {
|
||||
if (backend) {
|
||||
packageJson.dependencies = {
|
||||
...packageJson.dependencies,
|
||||
pg: "^8.12.0",
|
||||
pgvector: "^0.2.0",
|
||||
"@llamaindex/readers": "~3.1.4",
|
||||
};
|
||||
}
|
||||
|
||||
if (vectorDb === "qdrant") {
|
||||
packageJson.dependencies = {
|
||||
...packageJson.dependencies,
|
||||
"@qdrant/js-client-rest": "^1.11.0",
|
||||
};
|
||||
}
|
||||
if (vectorDb === "mongo") {
|
||||
packageJson.dependencies = {
|
||||
...packageJson.dependencies,
|
||||
mongodb: "^6.7.0",
|
||||
};
|
||||
}
|
||||
if (vectorDb && vectorDb in vectorDbDependencies) {
|
||||
packageJson.dependencies = {
|
||||
...packageJson.dependencies,
|
||||
...vectorDbDependencies[vectorDb],
|
||||
};
|
||||
}
|
||||
|
||||
if (vectorDb === "milvus") {
|
||||
packageJson.dependencies = {
|
||||
...packageJson.dependencies,
|
||||
"@zilliz/milvus2-sdk-node": "^2.4.6",
|
||||
};
|
||||
if (modelConfig.provider && modelConfig.provider in providerDependencies) {
|
||||
packageJson.dependencies = {
|
||||
...packageJson.dependencies,
|
||||
...providerDependencies[modelConfig.provider],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (observability === "traceloop") {
|
||||
@@ -328,6 +549,16 @@ async function updatePackageJson({
|
||||
};
|
||||
}
|
||||
|
||||
// if having custom server package tgz file, use it for testing @llamaindex/server
|
||||
const serverPackagePath = process.env.SERVER_PACKAGE_PATH;
|
||||
if (serverPackagePath && template === "llamaindexserver") {
|
||||
const relativePath = path.relative(process.cwd(), serverPackagePath);
|
||||
packageJson.dependencies = {
|
||||
...packageJson.dependencies,
|
||||
"@llamaindex/server": `file:${relativePath}`,
|
||||
};
|
||||
}
|
||||
|
||||
await fs.writeFile(
|
||||
packageJsonFile,
|
||||
JSON.stringify(packageJson, null, 2) + os.EOL,
|
||||
@@ -0,0 +1,42 @@
|
||||
// Migrate poetry to uv
|
||||
import { execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
import { red } from "picocolors";
|
||||
|
||||
export function isUvAvailable(): boolean {
|
||||
try {
|
||||
execSync("uv --version", { stdio: "ignore" });
|
||||
return true;
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function tryUvSync(): boolean {
|
||||
try {
|
||||
console.log("Syncing environment with pyproject.toml...");
|
||||
execSync(`uv sync`, {
|
||||
stdio: "inherit",
|
||||
});
|
||||
return true;
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function tryUvRun(command: string): boolean {
|
||||
try {
|
||||
// Use uv run <command>
|
||||
execSync(`uv run ${command}`, { stdio: "inherit" });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(red(`Failed to run ${command}. Error: ${error}`));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isHavingUvLockFile(): boolean {
|
||||
try {
|
||||
// Check if uv.lock exists in the current directory
|
||||
return fs.existsSync("uv.lock");
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import validateProjectName from "validate-npm-package-name";
|
||||
|
||||
export function validateNpmName(name: string): {
|
||||
@@ -1,40 +1,26 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { assetRelocator, copy } from "./copy";
|
||||
import { TemplateFramework } from "./types";
|
||||
|
||||
function renderDevcontainerContent(
|
||||
templatesDir: string,
|
||||
framework: TemplateFramework,
|
||||
frontend: boolean,
|
||||
) {
|
||||
const devcontainerJson: any = JSON.parse(
|
||||
fs.readFileSync(path.join(templatesDir, "devcontainer.json"), "utf8"),
|
||||
);
|
||||
|
||||
// Modify postCreateCommand
|
||||
if (frontend) {
|
||||
devcontainerJson.postCreateCommand =
|
||||
framework === "fastapi"
|
||||
? "cd backend && poetry install && cd ../frontend && npm install"
|
||||
: "cd backend && npm install && cd ../frontend && npm install";
|
||||
} else {
|
||||
devcontainerJson.postCreateCommand =
|
||||
framework === "fastapi" ? "poetry install" : "npm install";
|
||||
}
|
||||
devcontainerJson.postCreateCommand =
|
||||
framework === "fastapi" ? "poetry install" : "npm install";
|
||||
|
||||
// Modify containerEnv
|
||||
if (framework === "fastapi") {
|
||||
if (frontend) {
|
||||
devcontainerJson.containerEnv = {
|
||||
...devcontainerJson.containerEnv,
|
||||
PYTHONPATH: "${PYTHONPATH}:${workspaceFolder}/backend",
|
||||
};
|
||||
} else {
|
||||
devcontainerJson.containerEnv = {
|
||||
...devcontainerJson.containerEnv,
|
||||
PYTHONPATH: "${PYTHONPATH}:${workspaceFolder}",
|
||||
};
|
||||
}
|
||||
devcontainerJson.containerEnv = {
|
||||
...devcontainerJson.containerEnv,
|
||||
PYTHONPATH: "${PYTHONPATH}:${workspaceFolder}",
|
||||
};
|
||||
}
|
||||
|
||||
return JSON.stringify(devcontainerJson, null, 2);
|
||||
@@ -44,7 +30,6 @@ export const writeDevcontainer = async (
|
||||
root: string,
|
||||
templatesDir: string,
|
||||
framework: TemplateFramework,
|
||||
frontend: boolean,
|
||||
) => {
|
||||
const devcontainerDir = path.join(root, ".devcontainer");
|
||||
if (fs.existsSync(devcontainerDir)) {
|
||||
@@ -54,7 +39,6 @@ export const writeDevcontainer = async (
|
||||
const devcontainerContent = renderDevcontainerContent(
|
||||
templatesDir,
|
||||
framework,
|
||||
frontend,
|
||||
);
|
||||
fs.mkdirSync(devcontainerDir);
|
||||
await fs.promises.writeFile(
|
||||
@@ -62,3 +46,25 @@ export const writeDevcontainer = async (
|
||||
devcontainerContent,
|
||||
);
|
||||
};
|
||||
|
||||
export const copyVSCodeSettings = async (
|
||||
root: string,
|
||||
templatesDir: string,
|
||||
) => {
|
||||
const vscodeDir = path.join(root, ".vscode");
|
||||
await copy("vscode_settings.json", vscodeDir, {
|
||||
cwd: templatesDir,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
};
|
||||
|
||||
export const configVSCode = async (
|
||||
root: string,
|
||||
templatesDir: string,
|
||||
framework: TemplateFramework,
|
||||
) => {
|
||||
await writeDevcontainer(root, templatesDir, framework);
|
||||
if (framework === "fastapi") {
|
||||
await copyVSCodeSettings(root, templatesDir);
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { execSync } from "child_process";
|
||||
import { Command } from "commander";
|
||||
import fs from "fs";
|
||||
@@ -134,13 +133,6 @@ const program = new Command(packageJson.name)
|
||||
`
|
||||
|
||||
Select UI port.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
"--external-port <external>",
|
||||
`
|
||||
|
||||
Select external port.
|
||||
`,
|
||||
)
|
||||
.option(
|
||||
@@ -204,10 +196,17 @@ const program = new Command(packageJson.name)
|
||||
"--pro",
|
||||
`
|
||||
|
||||
Allow interactive selection of all features.
|
||||
Deprecated: Allow interactive selection of all features.
|
||||
`,
|
||||
false,
|
||||
)
|
||||
.option(
|
||||
"--use-case <useCase>",
|
||||
`
|
||||
|
||||
Select which use case to use for the multi-agent template (e.g: financial_report, blog).
|
||||
`,
|
||||
)
|
||||
.allowUnknownOption()
|
||||
.parse(process.argv);
|
||||
|
||||
@@ -215,7 +214,7 @@ const options = program.opts();
|
||||
|
||||
if (
|
||||
process.argv.includes("--no-llama-parse") ||
|
||||
options.template === "extractor"
|
||||
options.template === "reflex"
|
||||
) {
|
||||
options.useLlamaParse = false;
|
||||
}
|
||||
@@ -326,7 +325,6 @@ async function run(): Promise<void> {
|
||||
...answers,
|
||||
appPath: resolvedProjectPath,
|
||||
packageManager,
|
||||
externalPort: options.externalPort,
|
||||
});
|
||||
|
||||
if (answers.postInstallAction === "VSCode") {
|
||||
@@ -355,14 +353,7 @@ Please check ${cyan(
|
||||
}
|
||||
} else if (answers.postInstallAction === "runApp") {
|
||||
console.log(`Running app in ${root}...`);
|
||||
await runApp(
|
||||
root,
|
||||
answers.template,
|
||||
answers.frontend,
|
||||
answers.framework,
|
||||
options.port,
|
||||
options.externalPort,
|
||||
);
|
||||
await runApp(root, answers.template, answers.framework, options.port);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "create-llama",
|
||||
"version": "0.5.20",
|
||||
"description": "Create LlamaIndex-powered apps with one command",
|
||||
"keywords": [
|
||||
"rag",
|
||||
"llamaindex",
|
||||
"next.js"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/run-llama/create-llama",
|
||||
"directory": "packages/create-llama"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"create-llama": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"LICENSE.md"
|
||||
],
|
||||
"scripts": {
|
||||
"copy": "cp -r ../../README.md ../../LICENSE.md .",
|
||||
"build": "bash ./scripts/build.sh",
|
||||
"build:ncc": "pnpm run clean && ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
|
||||
"postbuild": "pnpm run copy",
|
||||
"clean": "rimraf --glob ./dist ./templates/**/__pycache__ ./templates/**/node_modules ./templates/**/poetry.lock",
|
||||
"dev": "ncc build ./index.ts -w -o dist/",
|
||||
"e2e": "playwright test",
|
||||
"e2e:python": "playwright test e2e/shared e2e/python",
|
||||
"e2e:typescript": "playwright test e2e/shared e2e/typescript",
|
||||
"pack-install": "bash ./scripts/pack.sh"
|
||||
},
|
||||
"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.4.2",
|
||||
"@types/tar": "6.1.5",
|
||||
"@types/validate-npm-package-name": "3.0.0",
|
||||
"async-retry": "1.3.1",
|
||||
"async-sema": "3.0.1",
|
||||
"ci-info": "github:watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
|
||||
"commander": "12.1.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.4.2",
|
||||
"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",
|
||||
"yaml": "2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.41.1",
|
||||
"@vercel/ncc": "0.38.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"typescript": "^5.3.3",
|
||||
"wait-port": "^1.1.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.0.5",
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
@@ -6,7 +6,7 @@ const defaults: Omit<QuestionArgs, "modelConfig"> = {
|
||||
framework: "nextjs",
|
||||
ui: "shadcn",
|
||||
frontend: false,
|
||||
llamaCloudKey: "",
|
||||
llamaCloudKey: undefined,
|
||||
useLlamaParse: false,
|
||||
communityProjectConfig: undefined,
|
||||
llamapack: "",
|
||||
@@ -49,7 +49,7 @@ export const getDataSourceChoices = (
|
||||
);
|
||||
}
|
||||
|
||||
if (framework === "fastapi" && template !== "extractor") {
|
||||
if (framework === "fastapi" && template !== "reflex") {
|
||||
choices.push({
|
||||
title: "Use website content (requires Chrome)",
|
||||
value: "web",
|
||||
@@ -1,18 +1,28 @@
|
||||
import ciInfo from "ci-info";
|
||||
import { bold, yellow } from "picocolors";
|
||||
import { getCIQuestionResults } from "./ci";
|
||||
import { askProQuestions } from "./questions";
|
||||
import { askSimpleQuestions } from "./simple";
|
||||
import { QuestionArgs, QuestionResults } from "./types";
|
||||
|
||||
export const isCI = ciInfo.isCI || process.env.PLAYWRIGHT_TEST === "1";
|
||||
|
||||
export const askQuestions = async (
|
||||
args: QuestionArgs,
|
||||
): Promise<QuestionResults> => {
|
||||
if (ciInfo.isCI) {
|
||||
if (isCI) {
|
||||
return await getCIQuestionResults(args);
|
||||
} else if (args.pro) {
|
||||
// TODO: refactor pro questions to return a result object
|
||||
console.log(
|
||||
yellow(
|
||||
`Pro mode is deprecated. Please use the new templates using the ${bold("LlamaIndexServer")} by not specifying pro mode.`,
|
||||
),
|
||||
);
|
||||
|
||||
await askProQuestions(args);
|
||||
return args as unknown as QuestionResults;
|
||||
}
|
||||
return await askSimpleQuestions(args);
|
||||
const results = await askSimpleQuestions(args);
|
||||
return results;
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
import { blue, green } from "picocolors";
|
||||
import { blue } from "picocolors";
|
||||
import prompts from "prompts";
|
||||
import { isCI } from ".";
|
||||
import { COMMUNITY_OWNER, COMMUNITY_REPO } from "../helpers/constant";
|
||||
import { EXAMPLE_FILE } from "../helpers/datasources";
|
||||
import { EXAMPLE_FILE, EXAMPLE_GDPR } from "../helpers/datasources";
|
||||
import { getAvailableLlamapackOptions } from "../helpers/llama-pack";
|
||||
import { askModelConfig } from "../helpers/providers";
|
||||
import { getProjectOptions } from "../helpers/repo";
|
||||
@@ -32,7 +33,7 @@ export const askProQuestions = async (program: QuestionArgs) => {
|
||||
title: "Multi-agent app (using workflows)",
|
||||
value: "multiagent",
|
||||
},
|
||||
{ title: "Structured Extractor", value: "extractor" },
|
||||
{ title: "Fullstack python template with Reflex", value: "reflex" },
|
||||
{
|
||||
title: `Community template from ${styledRepo}`,
|
||||
value: "community",
|
||||
@@ -88,15 +89,35 @@ export const askProQuestions = async (program: QuestionArgs) => {
|
||||
questionHandlers,
|
||||
);
|
||||
program.llamapack = llamapack;
|
||||
program.postInstallAction = await askPostInstallAction(program);
|
||||
if (!program.postInstallAction) {
|
||||
program.postInstallAction = await askPostInstallAction(program);
|
||||
}
|
||||
return; // early return - no further questions needed for llamapack projects
|
||||
}
|
||||
|
||||
if (program.template === "extractor") {
|
||||
// Extractor template only supports FastAPI, empty data sources, and llamacloud
|
||||
if (program.template === "reflex") {
|
||||
// Reflex template only supports FastAPI, empty data sources, and llamacloud
|
||||
// So we just use example file for extractor template, this allows user to choose vector database later
|
||||
program.dataSources = [EXAMPLE_FILE];
|
||||
program.framework = "fastapi";
|
||||
// Ask for which Reflex use case to use
|
||||
const { useCase } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "useCase",
|
||||
message: "Which use case would you like to build?",
|
||||
choices: [
|
||||
{ title: "Structured Extractor", value: "extractor" },
|
||||
{
|
||||
title: "Contract review (using Workflow)",
|
||||
value: "contract_review",
|
||||
},
|
||||
],
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
program.useCase = useCase;
|
||||
}
|
||||
|
||||
if (!program.framework) {
|
||||
@@ -120,24 +141,17 @@ export const askProQuestions = async (program: QuestionArgs) => {
|
||||
}
|
||||
|
||||
if (
|
||||
(program.framework === "express" || program.framework === "fastapi") &&
|
||||
program.framework === "fastapi" &&
|
||||
(program.template === "streaming" || program.template === "multiagent")
|
||||
) {
|
||||
// if a backend-only framework is selected, ask whether we should create a frontend
|
||||
if (program.frontend === undefined) {
|
||||
const styledNextJS = blue("NextJS");
|
||||
const styledBackend = green(
|
||||
program.framework === "express"
|
||||
? "Express "
|
||||
: program.framework === "fastapi"
|
||||
? "FastAPI (Python) "
|
||||
: "",
|
||||
);
|
||||
const { frontend } = await prompts({
|
||||
onState: onPromptState,
|
||||
type: "toggle",
|
||||
name: "frontend",
|
||||
message: `Would you like to generate a ${styledNextJS} frontend for your ${styledBackend}backend?`,
|
||||
message: `Would you like to generate a ${styledNextJS} frontend for your FastAPI backend?`,
|
||||
initial: false,
|
||||
active: "Yes",
|
||||
inactive: "No",
|
||||
@@ -175,6 +189,52 @@ export const askProQuestions = async (program: QuestionArgs) => {
|
||||
program.observability = observability;
|
||||
}
|
||||
|
||||
if (
|
||||
(program.template === "reflex" || program.template === "multiagent") &&
|
||||
!program.useCase
|
||||
) {
|
||||
const choices =
|
||||
program.template === "reflex"
|
||||
? [
|
||||
{ title: "Structured Extractor", value: "extractor" },
|
||||
{
|
||||
title: "Contract review (using Workflow)",
|
||||
value: "contract_review",
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: "Financial report (generate a financial report)",
|
||||
value: "financial_report",
|
||||
},
|
||||
{
|
||||
title: "Form filling (fill missing value in a CSV file)",
|
||||
value: "form_filling",
|
||||
},
|
||||
{ title: "Blog writer (Write a blog post)", value: "blog" },
|
||||
];
|
||||
|
||||
const { useCase } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "useCase",
|
||||
message: "Which use case would you like to use?",
|
||||
choices,
|
||||
initial: 0,
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
program.useCase = useCase;
|
||||
}
|
||||
|
||||
// Configure framework and data sources for Reflex template
|
||||
if (program.template === "reflex") {
|
||||
program.framework = "fastapi";
|
||||
|
||||
program.dataSources =
|
||||
program.useCase === "extractor" ? [EXAMPLE_FILE] : [EXAMPLE_GDPR];
|
||||
}
|
||||
|
||||
if (!program.modelConfig) {
|
||||
const modelConfig = await askModelConfig({
|
||||
openAiKey: program.openAiKey,
|
||||
@@ -198,8 +258,8 @@ export const askProQuestions = async (program: QuestionArgs) => {
|
||||
program.vectorDb = vectorDb;
|
||||
}
|
||||
|
||||
if (program.vectorDb === "llamacloud") {
|
||||
// When using a LlamaCloud index, don't ask for data sources just copy an example file
|
||||
if (program.vectorDb === "llamacloud" && program.dataSources.length === 0) {
|
||||
// When using a LlamaCloud index and no data sources are provided, just copy an example file
|
||||
program.dataSources = [EXAMPLE_FILE];
|
||||
}
|
||||
|
||||
@@ -330,11 +390,8 @@ export const askProQuestions = async (program: QuestionArgs) => {
|
||||
// default to use LlamaParse if using LlamaCloud
|
||||
program.useLlamaParse = true;
|
||||
} else {
|
||||
// Extractor template doesn't support LlamaParse and LlamaCloud right now (cannot use asyncio loop in Reflex)
|
||||
if (
|
||||
program.useLlamaParse === undefined &&
|
||||
program.template !== "extractor"
|
||||
) {
|
||||
// Reflex template doesn't support LlamaParse right now (cannot use asyncio loop in Reflex)
|
||||
if (program.useLlamaParse === undefined && program.template !== "reflex") {
|
||||
// if already set useLlamaParse, don't ask again
|
||||
if (program.dataSources.some((ds) => ds.type === "file")) {
|
||||
const { useLlamaParse } = await prompts(
|
||||
@@ -356,7 +413,7 @@ export const askProQuestions = async (program: QuestionArgs) => {
|
||||
|
||||
// Ask for LlamaCloud API key when using a LlamaCloud index or LlamaParse
|
||||
if (isUsingLlamaCloud || program.useLlamaParse) {
|
||||
if (!program.llamaCloudKey) {
|
||||
if (!program.llamaCloudKey && !isCI) {
|
||||
// if already set, don't ask again
|
||||
// Ask for LlamaCloud API key
|
||||
const { llamaCloudKey } = await prompts(
|
||||
@@ -396,5 +453,7 @@ export const askProQuestions = async (program: QuestionArgs) => {
|
||||
program.tools = tools;
|
||||
}
|
||||
|
||||
program.postInstallAction = await askPostInstallAction(program);
|
||||
if (!program.postInstallAction) {
|
||||
program.postInstallAction = await askPostInstallAction(program);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,193 @@
|
||||
import prompts from "prompts";
|
||||
import { EXAMPLE_10K_SEC_FILES, EXAMPLE_FILE } from "../helpers/datasources";
|
||||
import { askModelConfig } from "../helpers/providers";
|
||||
import { getTools } from "../helpers/tools";
|
||||
import { ModelConfig, TemplateFramework } from "../helpers/types";
|
||||
import { PureQuestionArgs, QuestionResults } from "./types";
|
||||
import { askPostInstallAction, questionHandlers } from "./utils";
|
||||
|
||||
type AppType =
|
||||
| "agentic_rag"
|
||||
| "financial_report"
|
||||
| "deep_research"
|
||||
| "code_generator"
|
||||
| "document_generator";
|
||||
|
||||
type SimpleAnswers = {
|
||||
appType: AppType;
|
||||
language: TemplateFramework;
|
||||
useLlamaCloud: boolean;
|
||||
llamaCloudKey?: string;
|
||||
};
|
||||
|
||||
export const askSimpleQuestions = async (
|
||||
args: PureQuestionArgs,
|
||||
): Promise<QuestionResults> => {
|
||||
const { appType } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "appType",
|
||||
message: "What use case do you want to build?",
|
||||
choices: [
|
||||
{
|
||||
title: "Agentic RAG",
|
||||
value: "agentic_rag",
|
||||
description:
|
||||
"Chatbot that answers questions based on provided documents.",
|
||||
},
|
||||
{
|
||||
title: "Financial Report",
|
||||
value: "financial_report",
|
||||
description:
|
||||
"Agent that analyzes data and generates visualizations by using a code interpreter.",
|
||||
},
|
||||
{
|
||||
title: "Deep Research",
|
||||
value: "deep_research",
|
||||
description:
|
||||
"Researches and analyzes provided documents from multiple perspectives, generating a comprehensive report with citations to support key findings and insights.",
|
||||
},
|
||||
{
|
||||
title: "Code Generator",
|
||||
value: "code_generator",
|
||||
description: "Build a Vercel v0 styled code generator.",
|
||||
},
|
||||
{
|
||||
title: "Document Generator",
|
||||
value: "document_generator",
|
||||
description: "Build a OpenAI canvas-styled document generator.",
|
||||
},
|
||||
],
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
|
||||
let language: TemplateFramework = "fastapi";
|
||||
let llamaCloudKey = args.llamaCloudKey;
|
||||
|
||||
let useLlamaCloud = false;
|
||||
|
||||
const { language: newLanguage } = await prompts(
|
||||
{
|
||||
type: "select",
|
||||
name: "language",
|
||||
message: "What language do you want to use?",
|
||||
choices: [
|
||||
{ title: "Python (FastAPI)", value: "fastapi" },
|
||||
{ title: "Typescript (NextJS)", value: "nextjs" },
|
||||
],
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
language = newLanguage;
|
||||
|
||||
if (appType !== "code_generator" && appType !== "document_generator") {
|
||||
const { useLlamaCloud: newUseLlamaCloud } = await prompts(
|
||||
{
|
||||
type: "toggle",
|
||||
name: "useLlamaCloud",
|
||||
message: "Do you want to use LlamaCloud services?",
|
||||
initial: false,
|
||||
active: "Yes",
|
||||
inactive: "No",
|
||||
hint: "see https://www.llamaindex.ai/enterprise for more info",
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
useLlamaCloud = newUseLlamaCloud;
|
||||
}
|
||||
|
||||
if (useLlamaCloud && !llamaCloudKey) {
|
||||
// Ask for LlamaCloud API key, if not set
|
||||
const { llamaCloudKey: newLlamaCloudKey } = await prompts(
|
||||
{
|
||||
type: "text",
|
||||
name: "llamaCloudKey",
|
||||
message:
|
||||
"Please provide your LlamaCloud API key (leave blank to skip):",
|
||||
},
|
||||
questionHandlers,
|
||||
);
|
||||
llamaCloudKey = newLlamaCloudKey || process.env.LLAMA_CLOUD_API_KEY;
|
||||
}
|
||||
|
||||
const results = await convertAnswers(args, {
|
||||
appType,
|
||||
language,
|
||||
useLlamaCloud,
|
||||
llamaCloudKey,
|
||||
});
|
||||
|
||||
results.postInstallAction = await askPostInstallAction(results);
|
||||
return results;
|
||||
};
|
||||
|
||||
const convertAnswers = async (
|
||||
args: PureQuestionArgs,
|
||||
answers: SimpleAnswers,
|
||||
): Promise<QuestionResults> => {
|
||||
const MODEL_GPT41: ModelConfig = {
|
||||
provider: "openai",
|
||||
apiKey: args.openAiKey,
|
||||
model: "gpt-4.1",
|
||||
embeddingModel: "text-embedding-3-large",
|
||||
dimensions: 1536,
|
||||
isConfigured(): boolean {
|
||||
return !!args.openAiKey;
|
||||
},
|
||||
};
|
||||
const lookup: Record<
|
||||
AppType,
|
||||
Pick<QuestionResults, "template" | "tools" | "dataSources" | "useCase"> & {
|
||||
modelConfig?: ModelConfig;
|
||||
}
|
||||
> = {
|
||||
agentic_rag: {
|
||||
template: "llamaindexserver",
|
||||
dataSources: [EXAMPLE_FILE],
|
||||
},
|
||||
financial_report: {
|
||||
template: "llamaindexserver",
|
||||
dataSources: EXAMPLE_10K_SEC_FILES,
|
||||
tools: getTools(["interpreter", "document_generator"]),
|
||||
modelConfig: MODEL_GPT41,
|
||||
},
|
||||
deep_research: {
|
||||
template: "llamaindexserver",
|
||||
dataSources: EXAMPLE_10K_SEC_FILES,
|
||||
tools: [],
|
||||
modelConfig: MODEL_GPT41,
|
||||
},
|
||||
code_generator: {
|
||||
template: "llamaindexserver",
|
||||
dataSources: [],
|
||||
tools: [],
|
||||
modelConfig: MODEL_GPT41,
|
||||
},
|
||||
document_generator: {
|
||||
template: "llamaindexserver",
|
||||
dataSources: [],
|
||||
tools: [],
|
||||
modelConfig: MODEL_GPT41,
|
||||
},
|
||||
};
|
||||
|
||||
const results = lookup[answers.appType];
|
||||
return {
|
||||
framework: answers.language,
|
||||
useCase: answers.appType,
|
||||
ui: "shadcn",
|
||||
llamaCloudKey: answers.llamaCloudKey,
|
||||
useLlamaParse: answers.useLlamaCloud,
|
||||
vectorDb: answers.useLlamaCloud ? "llamacloud" : "none",
|
||||
...results,
|
||||
modelConfig:
|
||||
results.modelConfig ??
|
||||
(await askModelConfig({
|
||||
openAiKey: args.openAiKey,
|
||||
askModels: args.askModels ?? false,
|
||||
framework: answers.language,
|
||||
})),
|
||||
frontend: true,
|
||||
};
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { InstallAppArgs } from "../create-app";
|
||||
|
||||
export type QuestionResults = Omit<
|
||||
InstallAppArgs,
|
||||
"appPath" | "packageManager" | "externalPort"
|
||||
"appPath" | "packageManager"
|
||||
>;
|
||||
|
||||
export type PureQuestionArgs = {
|
||||
+14
-15
@@ -1,5 +1,3 @@
|
||||
This is a [LlamaIndex](https://www.llamaindex.ai/) multi-agents project using [Workflows](https://docs.llamaindex.ai/en/stable/understanding/workflows/).
|
||||
|
||||
## Overview
|
||||
|
||||
This example is using three agents to generate a blog post:
|
||||
@@ -10,9 +8,9 @@ This example is using three agents to generate a blog post:
|
||||
|
||||
There are three different methods how the agents can interact to reach their goal:
|
||||
|
||||
1. [Choreography](./app/examples/choreography.py) - the agents decide themselves to delegate a task to another agent
|
||||
1. [Orchestrator](./app/examples/orchestrator.py) - a central orchestrator decides which agent should execute a task
|
||||
1. [Explicit Workflow](./app/examples/workflow.py) - a pre-defined workflow specific for the task is used to execute the tasks
|
||||
1. [Choreography](./app/agents/choreography.py) - the agents decide themselves to delegate a task to another agent
|
||||
1. [Orchestrator](./app/agents/orchestrator.py) - a central orchestrator decides which agent should execute a task
|
||||
1. [Explicit Workflow](./app/agents/workflow.py) - a pre-defined workflow specific for the task is used to execute the tasks
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -21,25 +19,23 @@ First, setup the environment with poetry:
|
||||
> **_Note:_** This step is not needed if you are using the dev-container.
|
||||
|
||||
```shell
|
||||
poetry install
|
||||
uv sync
|
||||
```
|
||||
|
||||
Then check the parameters that have been pre-configured in the `.env` file in this directory. (E.g. you might need to configure an `OPENAI_API_KEY` if you're using OpenAI as model provider).
|
||||
|
||||
Second, generate the embeddings of the documents in the `./data` directory:
|
||||
|
||||
```shell
|
||||
poetry run generate
|
||||
uv run generate
|
||||
```
|
||||
|
||||
Third, run the development server:
|
||||
|
||||
```shell
|
||||
poetry run python main.py
|
||||
uv run dev
|
||||
```
|
||||
|
||||
Per default, the example is using the explicit workflow. You can change the example by setting the `EXAMPLE_TYPE` environment variable to `choreography` or `orchestrator`.
|
||||
|
||||
The example provides one streaming API endpoint `/api/chat`.
|
||||
You can test the endpoint with the following curl request:
|
||||
|
||||
@@ -51,19 +47,22 @@ curl --location 'localhost:8000/api/chat' \
|
||||
|
||||
You can start editing the API by modifying `app/api/routers/chat.py` or `app/examples/workflow.py`. The API auto-updates as you save the files.
|
||||
|
||||
Open [http://localhost:8000/docs](http://localhost:8000/docs) with your browser to see the Swagger UI of the API.
|
||||
Open [http://localhost:8000](http://localhost:8000) with your browser to start the app.
|
||||
|
||||
The API allows CORS for all origins to simplify development. You can change this behavior by setting the `ENVIRONMENT` environment variable to `prod`:
|
||||
To start the app optimized for **production**, run:
|
||||
|
||||
```
|
||||
ENVIRONMENT=prod poetry run python main.py
|
||||
uv run prod
|
||||
```
|
||||
|
||||
## Deployments
|
||||
|
||||
For production deployments, check the [DEPLOY.md](DEPLOY.md) file.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about LlamaIndex, take a look at the following resources:
|
||||
|
||||
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
|
||||
- [Workflows Introduction](https://docs.llamaindex.ai/en/stable/understanding/workflows/) - learn about LlamaIndex workflows.
|
||||
|
||||
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
|
||||
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
from textwrap import dedent
|
||||
from typing import List, Optional
|
||||
|
||||
from app.agents.multi import AgentCallingAgent
|
||||
from app.agents.single import FunctionCallingAgent
|
||||
from app.examples.publisher import create_publisher
|
||||
from app.examples.researcher import create_researcher
|
||||
from app.agents.publisher import create_publisher
|
||||
from app.agents.researcher import create_researcher
|
||||
from app.workflows.multi import AgentCallingAgent
|
||||
from app.workflows.single import FunctionCallingAgent
|
||||
from llama_index.core.chat_engine.types import ChatMessage
|
||||
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
from textwrap import dedent
|
||||
from typing import List, Optional
|
||||
|
||||
from app.agents.multi import AgentOrchestrator
|
||||
from app.agents.single import FunctionCallingAgent
|
||||
from app.examples.publisher import create_publisher
|
||||
from app.examples.researcher import create_researcher
|
||||
from app.agents.publisher import create_publisher
|
||||
from app.agents.researcher import create_researcher
|
||||
from app.workflows.multi import AgentOrchestrator
|
||||
from app.workflows.single import FunctionCallingAgent
|
||||
from llama_index.core.chat_engine.types import ChatMessage
|
||||
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
from textwrap import dedent
|
||||
from typing import List, Tuple
|
||||
|
||||
from app.agents.single import FunctionCallingAgent
|
||||
from app.engine.tools import ToolFactory
|
||||
from app.workflows.single import FunctionCallingAgent
|
||||
from llama_index.core.chat_engine.types import ChatMessage
|
||||
from llama_index.core.tools import FunctionTool
|
||||
|
||||
@@ -11,11 +11,11 @@ def get_publisher_tools() -> Tuple[List[FunctionTool], str, str]:
|
||||
tools = []
|
||||
# Get configured tools from the tools.yaml file
|
||||
configured_tools = ToolFactory.from_env(map_result=True)
|
||||
if "document_generator" in configured_tools.keys():
|
||||
tools.extend(configured_tools["document_generator"])
|
||||
if "generate_document" in configured_tools.keys():
|
||||
tools.append(configured_tools["generate_document"])
|
||||
prompt_instructions = dedent("""
|
||||
Normally, reply the blog post content to the user directly.
|
||||
But if user requested to generate a file, use the document_generator tool to generate the file and reply the link to the file.
|
||||
But if user requested to generate a file, use the generate_document tool to generate the file and reply the link to the file.
|
||||
""")
|
||||
description = "Expert in publishing the blog post, able to publish the blog post in PDF or HTML format."
|
||||
else:
|
||||
+18
-33
@@ -1,52 +1,37 @@
|
||||
import os
|
||||
from textwrap import dedent
|
||||
from typing import List
|
||||
|
||||
from app.agents.single import FunctionCallingAgent
|
||||
from app.engine.index import IndexConfig, get_index
|
||||
from app.engine.tools import ToolFactory
|
||||
from app.workflows.single import FunctionCallingAgent
|
||||
from llama_index.core.chat_engine.types import ChatMessage
|
||||
from llama_index.core.tools import QueryEngineTool, ToolMetadata
|
||||
from app.engine.tools.query_engine import get_query_engine_tool
|
||||
|
||||
|
||||
def _create_query_engine_tool(params=None) -> QueryEngineTool:
|
||||
"""
|
||||
Provide an agent worker that can be used to query the index.
|
||||
"""
|
||||
# Add query tool if index exists
|
||||
index_config = IndexConfig(**(params or {}))
|
||||
index = get_index(index_config)
|
||||
if index is None:
|
||||
return None
|
||||
top_k = int(os.getenv("TOP_K", 0))
|
||||
query_engine = index.as_query_engine(
|
||||
**({"similarity_top_k": top_k} if top_k != 0 else {})
|
||||
)
|
||||
return QueryEngineTool(
|
||||
query_engine=query_engine,
|
||||
metadata=ToolMetadata(
|
||||
name="query_index",
|
||||
description="""
|
||||
Use this tool to retrieve information about the text corpus from the index.
|
||||
""",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _get_research_tools(**kwargs) -> QueryEngineTool:
|
||||
def _get_research_tools(**kwargs):
|
||||
"""
|
||||
Researcher take responsibility for retrieving information.
|
||||
Try init wikipedia or duckduckgo tool if available.
|
||||
"""
|
||||
tools = []
|
||||
query_engine_tool = _create_query_engine_tool(**kwargs)
|
||||
if query_engine_tool is not None:
|
||||
tools.append(query_engine_tool)
|
||||
researcher_tool_names = ["duckduckgo", "wikipedia.WikipediaToolSpec"]
|
||||
# Create query engine tool
|
||||
index_config = IndexConfig(**kwargs)
|
||||
index = get_index(index_config)
|
||||
if index is not None:
|
||||
query_engine_tool = get_query_engine_tool(index=index)
|
||||
if query_engine_tool is not None:
|
||||
tools.append(query_engine_tool)
|
||||
|
||||
# Create duckduckgo tool
|
||||
researcher_tool_names = [
|
||||
"duckduckgo_search",
|
||||
"duckduckgo_image_search",
|
||||
"wikipedia.WikipediaToolSpec",
|
||||
]
|
||||
configured_tools = ToolFactory.from_env(map_result=True)
|
||||
for tool_name, tool in configured_tools.items():
|
||||
if tool_name in researcher_tool_names:
|
||||
tools.extend(tool)
|
||||
tools.append(tool)
|
||||
return tools
|
||||
|
||||
|
||||
+6
-4
@@ -1,9 +1,9 @@
|
||||
from textwrap import dedent
|
||||
from typing import AsyncGenerator, List, Optional
|
||||
|
||||
from app.agents.single import AgentRunEvent, AgentRunResult, FunctionCallingAgent
|
||||
from app.examples.publisher import create_publisher
|
||||
from app.examples.researcher import create_researcher
|
||||
from app.agents.publisher import create_publisher
|
||||
from app.agents.researcher import create_researcher
|
||||
from app.workflows.single import AgentRunEvent, AgentRunResult, FunctionCallingAgent
|
||||
from llama_index.core.chat_engine.types import ChatMessage
|
||||
from llama_index.core.prompts import PromptTemplate
|
||||
from llama_index.core.settings import Settings
|
||||
@@ -238,7 +238,9 @@ class BlogPostWorkflow(Workflow):
|
||||
publisher: FunctionCallingAgent,
|
||||
) -> StopEvent:
|
||||
try:
|
||||
result: AgentRunResult = await self.run_agent(ctx, publisher, ev.input)
|
||||
result: AgentRunResult = await self.run_agent(
|
||||
ctx, publisher, ev.input, streaming=ctx.data["streaming"]
|
||||
)
|
||||
return StopEvent(result=result)
|
||||
except Exception as e:
|
||||
ctx.write_event_to_stream(
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
from .blog import create_workflow
|
||||
|
||||
__all__ = ["create_workflow"]
|
||||
+7
-6
@@ -2,19 +2,20 @@ import logging
|
||||
import os
|
||||
from typing import List, Optional
|
||||
|
||||
from app.examples.choreography import create_choreography
|
||||
from app.examples.orchestrator import create_orchestrator
|
||||
from app.examples.workflow import create_workflow
|
||||
from app.agents.choreography import create_choreography
|
||||
from app.agents.orchestrator import create_orchestrator
|
||||
from app.agents.workflow import create_workflow as create_blog_workflow
|
||||
from llama_index.core.chat_engine.types import ChatMessage
|
||||
from llama_index.core.workflow import Workflow
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
|
||||
|
||||
def get_chat_engine(
|
||||
def create_workflow(
|
||||
chat_history: Optional[List[ChatMessage]] = None, **kwargs
|
||||
) -> Workflow:
|
||||
# TODO: the EXAMPLE_TYPE could be passed as a chat config parameter?
|
||||
# Chat filters are not supported yet
|
||||
kwargs.pop("filters", None)
|
||||
agent_type = os.getenv("EXAMPLE_TYPE", "").lower()
|
||||
match agent_type:
|
||||
case "choreography":
|
||||
@@ -22,7 +23,7 @@ def get_chat_engine(
|
||||
case "orchestrator":
|
||||
agent = create_orchestrator(chat_history, **kwargs)
|
||||
case _:
|
||||
agent = create_workflow(chat_history, **kwargs)
|
||||
agent = create_blog_workflow(chat_history, **kwargs)
|
||||
|
||||
logger.info(f"Using agent pattern: {agent_type}")
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
from typing import Any, List
|
||||
|
||||
from app.agents.planner import StructuredPlannerAgent
|
||||
from app.agents.single import (
|
||||
from app.workflows.planner import StructuredPlannerAgent
|
||||
from app.workflows.single import (
|
||||
AgentRunResult,
|
||||
ContextAwareTool,
|
||||
FunctionCallingAgent,
|
||||
+2
-2
@@ -2,7 +2,7 @@ import uuid
|
||||
from enum import Enum
|
||||
from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from app.agents.single import AgentRunEvent, AgentRunResult, FunctionCallingAgent
|
||||
from app.workflows.single import AgentRunEvent, AgentRunResult, FunctionCallingAgent
|
||||
from llama_index.core.agent.runner.planner import (
|
||||
DEFAULT_INITIAL_PLAN_PROMPT,
|
||||
DEFAULT_PLAN_REFINE_PROMPT,
|
||||
@@ -317,7 +317,7 @@ class Planner:
|
||||
# gather completed sub-tasks and response pairs
|
||||
completed_outputs_str = ""
|
||||
for sub_task_name, task_output in completed_sub_task.items():
|
||||
task_str = f"{sub_task_name}:\n" f"\t{task_output!s}\n"
|
||||
task_str = f"{sub_task_name}:\n\t{task_output!s}\n"
|
||||
completed_outputs_str += task_str
|
||||
|
||||
# get a string for the remaining sub-tasks
|
||||
+20
-9
@@ -1,4 +1,5 @@
|
||||
from abc import abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Any, AsyncGenerator, List, Optional
|
||||
|
||||
from llama_index.core.llms import ChatMessage, ChatResponse
|
||||
@@ -15,7 +16,7 @@ from llama_index.core.workflow import (
|
||||
Workflow,
|
||||
step,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class InputEvent(Event):
|
||||
@@ -26,17 +27,27 @@ class ToolCallEvent(Event):
|
||||
tool_calls: list[ToolSelection]
|
||||
|
||||
|
||||
class AgentRunEventType(Enum):
|
||||
TEXT = "text"
|
||||
PROGRESS = "progress"
|
||||
|
||||
|
||||
class AgentRunEvent(Event):
|
||||
name: str
|
||||
_msg: str
|
||||
msg: str
|
||||
event_type: AgentRunEventType = Field(default=AgentRunEventType.TEXT)
|
||||
data: Optional[dict] = None
|
||||
|
||||
@property
|
||||
def msg(self):
|
||||
return self._msg
|
||||
|
||||
@msg.setter
|
||||
def msg(self, value):
|
||||
self._msg = value
|
||||
def to_response(self) -> dict:
|
||||
return {
|
||||
"type": "agent",
|
||||
"data": {
|
||||
"agent": self.name,
|
||||
"type": self.event_type.value,
|
||||
"text": self.msg,
|
||||
"data": self.data,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class AgentRunResult(BaseModel):
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
This is a [LlamaIndex](https://www.llamaindex.ai/) multi-agents project using [Workflows](https://docs.llamaindex.ai/en/stable/understanding/workflows/).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, setup the environment with poetry:
|
||||
|
||||
> **_Note:_** This step is not needed if you are using the dev-container.
|
||||
|
||||
```shell
|
||||
uv sync
|
||||
```
|
||||
|
||||
Then check the parameters that have been pre-configured in the `.env` file in this directory. (E.g. you might need to configure an `OPENAI_API_KEY` if you're using OpenAI as model provider).
|
||||
Second, generate the embeddings of the documents in the `./data` directory:
|
||||
|
||||
```shell
|
||||
uv run generate
|
||||
```
|
||||
|
||||
Third, run the development server:
|
||||
|
||||
```shell
|
||||
uv run dev
|
||||
```
|
||||
|
||||
## Use Case: Deep Research over own documents
|
||||
|
||||
The workflow performs deep research by retrieving and analyzing documents from the [data](./data) directory from multiple perspectives. The project includes a sample PDF about AI investment in 2024 to help you get started. You can also add your own documents by placing them in the data directory and running the generate script again to index them.
|
||||
|
||||
After starting the server, go to [http://localhost:8000](http://localhost:8000) and send a message to the agent to write a blog post.
|
||||
E.g: "AI investment in 2024"
|
||||
|
||||
To update the workflow, you can edit the [deep_research.py](./app/workflows/deep_research.py) file.
|
||||
|
||||
By default, the workflow retrieves 10 results from your documents. To customize the amount of information covered in the answer, you can adjust the `TOP_K` environment variable in the `.env` file. A higher value will retrieve more results from your documents, potentially providing more comprehensive answers.
|
||||
|
||||
## Deployments
|
||||
|
||||
For production deployments, check the [DEPLOY.md](DEPLOY.md) file.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about LlamaIndex, take a look at the following resources:
|
||||
|
||||
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
|
||||
- [Workflows Introduction](https://docs.llamaindex.ai/en/stable/understanding/workflows/) - learn about LlamaIndex workflows.
|
||||
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
from .deep_research import create_workflow
|
||||
|
||||
__all__ = ["create_workflow"]
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
from typing import List, Literal, Optional
|
||||
|
||||
from llama_index.core.base.llms.types import (
|
||||
CompletionResponse,
|
||||
CompletionResponseAsyncGen,
|
||||
)
|
||||
from llama_index.core.memory.simple_composable_memory import SimpleComposableMemory
|
||||
from llama_index.core.prompts import PromptTemplate
|
||||
from llama_index.core.schema import MetadataMode, Node, NodeWithScore
|
||||
from llama_index.core.settings import Settings
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AnalysisDecision(BaseModel):
|
||||
decision: Literal["research", "write", "cancel"] = Field(
|
||||
description="Whether to continue research, write a report, or cancel the research after several retries"
|
||||
)
|
||||
research_questions: Optional[List[str]] = Field(
|
||||
description="""
|
||||
If the decision is to research, provide a list of questions to research that related to the user request.
|
||||
Maximum 3 questions. Set to null or empty if writing a report or cancel the research.
|
||||
""",
|
||||
default_factory=list,
|
||||
)
|
||||
cancel_reason: Optional[str] = Field(
|
||||
description="The reason for cancellation if the decision is to cancel research.",
|
||||
default=None,
|
||||
)
|
||||
|
||||
|
||||
async def plan_research(
|
||||
memory: SimpleComposableMemory,
|
||||
context_nodes: List[Node],
|
||||
user_request: str,
|
||||
total_questions: int,
|
||||
) -> AnalysisDecision:
|
||||
analyze_prompt = """
|
||||
You are a professor who is guiding a researcher to research a specific request/problem.
|
||||
Your task is to decide on a research plan for the researcher.
|
||||
|
||||
The possible actions are:
|
||||
+ Provide a list of questions for the researcher to investigate, with the purpose of clarifying the request.
|
||||
+ Write a report if the researcher has already gathered enough research on the topic and can resolve the initial request.
|
||||
+ Cancel the research if most of the answers from researchers indicate there is insufficient information to research the request. Do not attempt more than 3 research iterations or too many questions.
|
||||
|
||||
The workflow should be:
|
||||
+ Always begin by providing some initial questions for the researcher to investigate.
|
||||
+ Analyze the provided answers against the initial topic/request. If the answers are insufficient to resolve the initial request, provide additional questions for the researcher to investigate.
|
||||
+ If the answers are sufficient to resolve the initial request, instruct the researcher to write a report.
|
||||
|
||||
Here are the context:
|
||||
<Collected information>
|
||||
{context_str}
|
||||
</Collected information>
|
||||
|
||||
<Conversation context>
|
||||
{conversation_context}
|
||||
</Conversation context>
|
||||
|
||||
{enhanced_prompt}
|
||||
|
||||
Now, provide your decision in the required format for this user request:
|
||||
<User request>
|
||||
{user_request}
|
||||
</User request>
|
||||
"""
|
||||
# Manually craft the prompt to avoid LLM hallucination
|
||||
enhanced_prompt = ""
|
||||
if total_questions == 0:
|
||||
# Avoid writing a report without any research context
|
||||
enhanced_prompt = """
|
||||
|
||||
The student has no questions to research. Let start by asking some questions.
|
||||
"""
|
||||
elif total_questions > 6:
|
||||
# Avoid asking too many questions (when the data is not ready for writing a report)
|
||||
enhanced_prompt = f"""
|
||||
|
||||
The student has researched {total_questions} questions. Should cancel the research if the context is not enough to write a report.
|
||||
"""
|
||||
|
||||
conversation_context = "\n".join(
|
||||
[f"{message.role}: {message.content}" for message in memory.get_all()]
|
||||
)
|
||||
context_str = "\n".join(
|
||||
[node.get_content(metadata_mode=MetadataMode.LLM) for node in context_nodes]
|
||||
)
|
||||
res = await Settings.llm.astructured_predict(
|
||||
output_cls=AnalysisDecision,
|
||||
prompt=PromptTemplate(template=analyze_prompt),
|
||||
user_request=user_request,
|
||||
context_str=context_str,
|
||||
conversation_context=conversation_context,
|
||||
enhanced_prompt=enhanced_prompt,
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
async def research(
|
||||
question: str,
|
||||
context_nodes: List[NodeWithScore],
|
||||
) -> str:
|
||||
prompt = """
|
||||
You are a researcher who is in the process of answering the question.
|
||||
The purpose is to answer the question based on the collected information, without using prior knowledge or making up any new information.
|
||||
Always add citations to the sentence/point/paragraph using the id of the provided content.
|
||||
The citation should follow this format: [citation:id]() where id is the id of the content.
|
||||
|
||||
E.g:
|
||||
If we have a context like this:
|
||||
<Citation id='abc-xyz'>
|
||||
Baby llama is called cria
|
||||
</Citation id='abc-xyz'>
|
||||
|
||||
And your answer uses the content, then the citation should be:
|
||||
- Baby llama is called cria [citation:abc-xyz]()
|
||||
|
||||
Here is the provided context for the question:
|
||||
<Collected information>
|
||||
{context_str}
|
||||
</Collected information>`
|
||||
|
||||
No prior knowledge, just use the provided context to answer the question: {question}
|
||||
"""
|
||||
context_str = "\n".join(
|
||||
[_get_text_node_content_for_citation(node) for node in context_nodes]
|
||||
)
|
||||
res = await Settings.llm.acomplete(
|
||||
prompt=prompt.format(question=question, context_str=context_str),
|
||||
)
|
||||
return res.text
|
||||
|
||||
|
||||
async def write_report(
|
||||
memory: SimpleComposableMemory,
|
||||
user_request: str,
|
||||
stream: bool = False,
|
||||
) -> CompletionResponse | CompletionResponseAsyncGen:
|
||||
report_prompt = """
|
||||
You are a researcher writing a report based on a user request and the research context.
|
||||
You have researched various perspectives related to the user request.
|
||||
The report should provide a comprehensive outline covering all important points from the researched perspectives.
|
||||
Create a well-structured outline for the research report that covers all the answers.
|
||||
|
||||
# IMPORTANT when writing in markdown format:
|
||||
+ Use tables or figures where appropriate to enhance presentation.
|
||||
+ Preserve all citation syntax (the `[citation:id]()` parts in the provided context). Keep these citations in the final report - no separate reference section is needed.
|
||||
+ Do not add links, a table of contents, or a references section to the report.
|
||||
|
||||
<User request>
|
||||
{user_request}
|
||||
</User request>
|
||||
|
||||
<Research context>
|
||||
{research_context}
|
||||
</Research context>
|
||||
|
||||
Now, write a report addressing the user request based on the research provided following the format and guidelines above.
|
||||
"""
|
||||
research_context = "\n".join(
|
||||
[f"{message.role}: {message.content}" for message in memory.get_all()]
|
||||
)
|
||||
|
||||
llm_complete_func = (
|
||||
Settings.llm.astream_complete if stream else Settings.llm.acomplete
|
||||
)
|
||||
|
||||
res = await llm_complete_func(
|
||||
prompt=report_prompt.format(
|
||||
user_request=user_request,
|
||||
research_context=research_context,
|
||||
),
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
def _get_text_node_content_for_citation(node: NodeWithScore) -> str:
|
||||
"""
|
||||
Construct node content for LLM with citation flag.
|
||||
"""
|
||||
node_id = node.node.node_id
|
||||
content = f"<Citation id='{node_id}'>\n{node.get_content(metadata_mode=MetadataMode.LLM)}</Citation id='{node_id}'>"
|
||||
return content
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from llama_index.core.indices.base import BaseIndex
|
||||
from llama_index.core.memory import ChatMemoryBuffer
|
||||
from llama_index.core.memory.simple_composable_memory import SimpleComposableMemory
|
||||
from llama_index.core.schema import Node
|
||||
from llama_index.core.types import ChatMessage, MessageRole
|
||||
from llama_index.core.workflow import (
|
||||
Context,
|
||||
StartEvent,
|
||||
StopEvent,
|
||||
Workflow,
|
||||
step,
|
||||
)
|
||||
|
||||
from app.engine.index import IndexConfig, get_index
|
||||
from app.workflows.agents import plan_research, research, write_report
|
||||
from app.workflows.events import SourceNodesEvent
|
||||
from app.workflows.models import (
|
||||
CollectAnswersEvent,
|
||||
DataEvent,
|
||||
PlanResearchEvent,
|
||||
ReportEvent,
|
||||
ResearchEvent,
|
||||
)
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
def create_workflow(
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> Workflow:
|
||||
index_config = IndexConfig(**params)
|
||||
index = get_index(index_config)
|
||||
if index is None:
|
||||
raise ValueError(
|
||||
"Index is not found. Try run generation script to create the index first."
|
||||
)
|
||||
|
||||
return DeepResearchWorkflow(
|
||||
index=index,
|
||||
timeout=120.0,
|
||||
)
|
||||
|
||||
|
||||
class DeepResearchWorkflow(Workflow):
|
||||
"""
|
||||
A workflow to research and analyze documents from multiple perspectives and write a comprehensive report.
|
||||
|
||||
Requirements:
|
||||
- An indexed documents containing the knowledge base related to the topic
|
||||
|
||||
Steps:
|
||||
1. Retrieve information from the knowledge base
|
||||
2. Analyze the retrieved information and provide questions for answering
|
||||
3. Answer the questions
|
||||
4. Write the report based on the research results
|
||||
"""
|
||||
|
||||
memory: SimpleComposableMemory
|
||||
context_nodes: List[Node]
|
||||
index: BaseIndex
|
||||
user_request: str
|
||||
stream: bool = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
index: BaseIndex,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.index = index
|
||||
self.context_nodes = []
|
||||
self.memory = SimpleComposableMemory.from_defaults(
|
||||
primary_memory=ChatMemoryBuffer.from_defaults(),
|
||||
)
|
||||
|
||||
@step
|
||||
async def retrieve(self, ctx: Context, ev: StartEvent) -> PlanResearchEvent:
|
||||
"""
|
||||
Initiate the workflow: memory, tools, agent
|
||||
"""
|
||||
self.stream = ev.get("stream", True)
|
||||
self.user_request = ev.get("user_msg")
|
||||
chat_history = ev.get("chat_history")
|
||||
if chat_history is not None:
|
||||
self.memory.put_messages(chat_history)
|
||||
|
||||
await ctx.set("total_questions", 0)
|
||||
|
||||
# Add user message to memory
|
||||
self.memory.put_messages(
|
||||
messages=[
|
||||
ChatMessage(
|
||||
role=MessageRole.USER,
|
||||
content=self.user_request,
|
||||
)
|
||||
]
|
||||
)
|
||||
ctx.write_event_to_stream(
|
||||
DataEvent(
|
||||
type="deep_research_event",
|
||||
data={
|
||||
"event": "retrieve",
|
||||
"state": "inprogress",
|
||||
},
|
||||
)
|
||||
)
|
||||
retriever = self.index.as_retriever(
|
||||
similarity_top_k=int(os.getenv("TOP_K", 10)),
|
||||
)
|
||||
nodes = retriever.retrieve(self.user_request)
|
||||
self.context_nodes.extend(nodes)
|
||||
ctx.write_event_to_stream(
|
||||
DataEvent(
|
||||
type="deep_research_event",
|
||||
data={
|
||||
"event": "retrieve",
|
||||
"state": "done",
|
||||
},
|
||||
)
|
||||
)
|
||||
# Send source nodes to the stream
|
||||
# Use SourceNodesEvent to display source nodes in the UI.
|
||||
ctx.write_event_to_stream(
|
||||
SourceNodesEvent(
|
||||
nodes=nodes,
|
||||
)
|
||||
)
|
||||
return PlanResearchEvent()
|
||||
|
||||
@step
|
||||
async def analyze(
|
||||
self, ctx: Context, ev: PlanResearchEvent
|
||||
) -> ResearchEvent | ReportEvent | StopEvent:
|
||||
"""
|
||||
Analyze the retrieved information
|
||||
"""
|
||||
logger.info("Analyzing the retrieved information")
|
||||
ctx.write_event_to_stream(
|
||||
DataEvent(
|
||||
type="deep_research_event",
|
||||
data={
|
||||
"event": "analyze",
|
||||
"state": "inprogress",
|
||||
},
|
||||
)
|
||||
)
|
||||
total_questions = await ctx.get("total_questions")
|
||||
res = await plan_research(
|
||||
memory=self.memory,
|
||||
context_nodes=self.context_nodes,
|
||||
user_request=self.user_request,
|
||||
total_questions=total_questions,
|
||||
)
|
||||
if res.decision == "cancel":
|
||||
ctx.write_event_to_stream(
|
||||
DataEvent(
|
||||
type="deep_research_event",
|
||||
data={
|
||||
"event": "analyze",
|
||||
"state": "done",
|
||||
},
|
||||
)
|
||||
)
|
||||
return StopEvent(
|
||||
result=res.cancel_reason,
|
||||
)
|
||||
elif res.decision == "write":
|
||||
# Writing a report without any research context is not allowed.
|
||||
# It's a LLM hallucination.
|
||||
if total_questions == 0:
|
||||
ctx.write_event_to_stream(
|
||||
DataEvent(
|
||||
type="deep_research_event",
|
||||
data={
|
||||
"event": "analyze",
|
||||
"state": "done",
|
||||
},
|
||||
)
|
||||
)
|
||||
return StopEvent(
|
||||
result="Sorry, I have a problem when analyzing the retrieved information. Please try again.",
|
||||
)
|
||||
|
||||
self.memory.put(
|
||||
message=ChatMessage(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content="No more idea to analyze. We should report the answers.",
|
||||
)
|
||||
)
|
||||
ctx.send_event(ReportEvent())
|
||||
else:
|
||||
total_questions += len(res.research_questions)
|
||||
await ctx.set("total_questions", total_questions) # For tracking
|
||||
await ctx.set(
|
||||
"waiting_questions", len(res.research_questions)
|
||||
) # For waiting questions to be answered
|
||||
self.memory.put(
|
||||
message=ChatMessage(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content="We need to find answers to the following questions:\n"
|
||||
+ "\n".join(res.research_questions),
|
||||
)
|
||||
)
|
||||
for question in res.research_questions:
|
||||
question_id = str(uuid.uuid4())
|
||||
ctx.write_event_to_stream(
|
||||
DataEvent(
|
||||
type="deep_research_event",
|
||||
data={
|
||||
"event": "answer",
|
||||
"state": "pending",
|
||||
"id": question_id,
|
||||
"question": question,
|
||||
"answer": None,
|
||||
},
|
||||
)
|
||||
)
|
||||
ctx.send_event(
|
||||
ResearchEvent(
|
||||
question_id=question_id,
|
||||
question=question,
|
||||
context_nodes=self.context_nodes,
|
||||
)
|
||||
)
|
||||
ctx.write_event_to_stream(
|
||||
DataEvent(
|
||||
type="deep_research_event",
|
||||
data={
|
||||
"event": "analyze",
|
||||
"state": "done",
|
||||
},
|
||||
)
|
||||
)
|
||||
return None
|
||||
|
||||
@step(num_workers=2)
|
||||
async def answer(self, ctx: Context, ev: ResearchEvent) -> CollectAnswersEvent:
|
||||
"""
|
||||
Answer the question
|
||||
"""
|
||||
ctx.write_event_to_stream(
|
||||
DataEvent(
|
||||
type="deep_research_event",
|
||||
data={
|
||||
"event": "answer",
|
||||
"state": "inprogress",
|
||||
"id": ev.question_id,
|
||||
"question": ev.question,
|
||||
},
|
||||
)
|
||||
)
|
||||
try:
|
||||
answer = await research(
|
||||
context_nodes=ev.context_nodes,
|
||||
question=ev.question,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error answering question {ev.question}: {e}")
|
||||
answer = f"Got error when answering the question: {ev.question}"
|
||||
ctx.write_event_to_stream(
|
||||
DataEvent(
|
||||
type="deep_research_event",
|
||||
data={
|
||||
"event": "answer",
|
||||
"state": "done",
|
||||
"id": ev.question_id,
|
||||
"question": ev.question,
|
||||
"answer": answer,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
return CollectAnswersEvent(
|
||||
question_id=ev.question_id,
|
||||
question=ev.question,
|
||||
answer=answer,
|
||||
)
|
||||
|
||||
@step
|
||||
async def collect_answers(
|
||||
self, ctx: Context, ev: CollectAnswersEvent
|
||||
) -> PlanResearchEvent:
|
||||
"""
|
||||
Collect answers to all questions
|
||||
"""
|
||||
num_questions = await ctx.get("waiting_questions")
|
||||
results = ctx.collect_events(
|
||||
ev,
|
||||
expected=[CollectAnswersEvent] * num_questions,
|
||||
)
|
||||
if results is None:
|
||||
return None
|
||||
for result in results:
|
||||
self.memory.put(
|
||||
message=ChatMessage(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content=f"<Question>{result.question}</Question>\n<Answer>{result.answer}</Answer>",
|
||||
)
|
||||
)
|
||||
await ctx.set("waiting_questions", 0)
|
||||
self.memory.put(
|
||||
message=ChatMessage(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content="Researched all the questions. Now, i need to analyze if it's ready to write a report or need to research more.",
|
||||
)
|
||||
)
|
||||
return PlanResearchEvent()
|
||||
|
||||
@step
|
||||
async def report(self, ctx: Context, ev: ReportEvent) -> StopEvent:
|
||||
"""
|
||||
Report the answers
|
||||
"""
|
||||
res = await write_report(
|
||||
memory=self.memory,
|
||||
user_request=self.user_request,
|
||||
stream=self.stream,
|
||||
)
|
||||
return StopEvent(
|
||||
result=res,
|
||||
)
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
from typing import List, Literal, Optional
|
||||
|
||||
from llama_index.core.schema import NodeWithScore
|
||||
from llama_index.core.workflow import Event
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
# Workflow events
|
||||
class PlanResearchEvent(Event):
|
||||
pass
|
||||
|
||||
|
||||
class ResearchEvent(Event):
|
||||
question_id: str
|
||||
question: str
|
||||
context_nodes: List[NodeWithScore]
|
||||
|
||||
|
||||
class CollectAnswersEvent(Event):
|
||||
question_id: str
|
||||
question: str
|
||||
answer: str
|
||||
|
||||
|
||||
class ReportEvent(Event):
|
||||
pass
|
||||
|
||||
|
||||
# Events that are streamed to the frontend and rendered there
|
||||
class DeepResearchEventData(BaseModel):
|
||||
event: Literal["retrieve", "analyze", "answer"]
|
||||
state: Literal["pending", "inprogress", "done", "error"]
|
||||
id: Optional[str] = None
|
||||
question: Optional[str] = None
|
||||
answer: Optional[str] = None
|
||||
|
||||
|
||||
class DataEvent(Event):
|
||||
type: Literal["deep_research_event"]
|
||||
data: DeepResearchEventData
|
||||
|
||||
def to_response(self):
|
||||
return self.model_dump()
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
This is a [LlamaIndex](https://www.llamaindex.ai/) multi-agents project using [Workflows](https://docs.llamaindex.ai/en/stable/understanding/workflows/).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, setup the environment with poetry:
|
||||
|
||||
> **_Note:_** This step is not needed if you are using the dev-container.
|
||||
|
||||
```shell
|
||||
uv sync
|
||||
```
|
||||
|
||||
Then check the parameters that have been pre-configured in the `.env` file in this directory. (E.g. you might need to configure an `OPENAI_API_KEY` if you're using OpenAI as model provider and `E2B_API_KEY` for the [E2B's code interpreter tool](https://e2b.dev/docs)).
|
||||
|
||||
Second, generate the embeddings of the documents in the `./data` directory:
|
||||
|
||||
```shell
|
||||
uv run generate
|
||||
```
|
||||
|
||||
Third, run the development server:
|
||||
|
||||
```shell
|
||||
uv run dev
|
||||
```
|
||||
|
||||
The example provides one streaming API endpoint `/api/chat`.
|
||||
You can test the endpoint with the following curl request:
|
||||
|
||||
```
|
||||
curl --location 'localhost:8000/api/chat' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "messages": [{ "role": "user", "content": "Create a report comparing the finances of Apple and Tesla" }] }'
|
||||
```
|
||||
|
||||
You can start editing the API by modifying `app/api/routers/chat.py` or `app/workflows/financial_report.py`. The API auto-updates as you save the files.
|
||||
|
||||
Open [http://localhost:8000](http://localhost:8000) with your browser to start the app.
|
||||
|
||||
To start the app optimized for **production**, run:
|
||||
|
||||
```
|
||||
uv run prod
|
||||
```
|
||||
|
||||
## Deployments
|
||||
|
||||
For production deployments, check the [DEPLOY.md](DEPLOY.md) file.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about LlamaIndex, take a look at the following resources:
|
||||
|
||||
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
|
||||
- [Workflows Introduction](https://docs.llamaindex.ai/en/stable/understanding/workflows/) - learn about LlamaIndex workflows.
|
||||
|
||||
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
from .financial_report import create_workflow
|
||||
|
||||
__all__ = ["create_workflow"]
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from llama_index.core import Settings
|
||||
from llama_index.core.base.llms.types import ChatMessage, MessageRole
|
||||
from llama_index.core.llms.function_calling import FunctionCallingLLM
|
||||
from llama_index.core.memory import ChatMemoryBuffer
|
||||
from llama_index.core.tools import FunctionTool, QueryEngineTool, ToolSelection
|
||||
from llama_index.core.workflow import (
|
||||
Context,
|
||||
Event,
|
||||
StartEvent,
|
||||
StopEvent,
|
||||
Workflow,
|
||||
step,
|
||||
)
|
||||
|
||||
from app.engine.index import IndexConfig, get_index
|
||||
from app.engine.tools import ToolFactory
|
||||
from app.engine.tools.query_engine import get_query_engine_tool
|
||||
from app.workflows.events import AgentRunEvent
|
||||
from app.workflows.tools import (
|
||||
call_tools,
|
||||
chat_with_tools,
|
||||
)
|
||||
|
||||
|
||||
def create_workflow(
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> Workflow:
|
||||
# Create query engine tool
|
||||
index_config = IndexConfig(**params)
|
||||
index = get_index(index_config)
|
||||
if index is None:
|
||||
raise ValueError(
|
||||
"Index is not found. Try run generation script to create the index first."
|
||||
)
|
||||
query_engine_tool = get_query_engine_tool(index=index)
|
||||
|
||||
configured_tools: Dict[str, FunctionTool] = ToolFactory.from_env(map_result=True) # type: ignore
|
||||
code_interpreter_tool = configured_tools.get("interpret")
|
||||
document_generator_tool = configured_tools.get("generate_document")
|
||||
|
||||
return FinancialReportWorkflow(
|
||||
query_engine_tool=query_engine_tool,
|
||||
code_interpreter_tool=code_interpreter_tool,
|
||||
document_generator_tool=document_generator_tool,
|
||||
)
|
||||
|
||||
|
||||
class InputEvent(Event):
|
||||
input: List[ChatMessage]
|
||||
response: bool = False
|
||||
|
||||
|
||||
class ResearchEvent(Event):
|
||||
input: list[ToolSelection]
|
||||
|
||||
|
||||
class AnalyzeEvent(Event):
|
||||
input: list[ToolSelection] | ChatMessage
|
||||
|
||||
|
||||
class ReportEvent(Event):
|
||||
input: list[ToolSelection]
|
||||
|
||||
|
||||
class FinancialReportWorkflow(Workflow):
|
||||
"""
|
||||
A workflow to generate a financial report using indexed documents.
|
||||
|
||||
Requirements:
|
||||
- Indexed documents containing financial data and a query engine tool to search them
|
||||
- A code interpreter tool to analyze data and generate reports
|
||||
- A document generator tool to create report files
|
||||
|
||||
Steps:
|
||||
1. LLM Input: The LLM determines the next step based on function calling.
|
||||
For example, if the model requests the query engine tool, it returns a ResearchEvent;
|
||||
if it requests document generation, it returns a ReportEvent.
|
||||
2. Research: Uses the query engine to find relevant chunks from indexed documents.
|
||||
After gathering information, it requests analysis (step 3).
|
||||
3. Analyze: Uses a custom prompt to analyze research results and can call the code
|
||||
interpreter tool for visualization or calculation. Returns results to the LLM.
|
||||
4. Report: Uses the document generator tool to create a report. Returns results to the LLM.
|
||||
"""
|
||||
|
||||
_default_system_prompt = """
|
||||
You are a financial analyst who are given a set of tools to help you.
|
||||
It's good to using appropriate tools for the user request and always use the information from the tools, don't make up anything yourself.
|
||||
For the query engine tool, you should break down the user request into a list of queries and call the tool with the queries.
|
||||
"""
|
||||
stream: bool = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
query_engine_tool: QueryEngineTool,
|
||||
code_interpreter_tool: FunctionTool,
|
||||
document_generator_tool: FunctionTool,
|
||||
llm: Optional[FunctionCallingLLM] = None,
|
||||
timeout: int = 360,
|
||||
system_prompt: Optional[str] = None,
|
||||
):
|
||||
super().__init__(timeout=timeout)
|
||||
self.system_prompt = system_prompt or self._default_system_prompt
|
||||
self.query_engine_tool = query_engine_tool
|
||||
self.code_interpreter_tool = code_interpreter_tool
|
||||
self.document_generator_tool = document_generator_tool
|
||||
assert query_engine_tool is not None, (
|
||||
"Query engine tool is not found. Try run generation script or upload a document file first."
|
||||
)
|
||||
assert code_interpreter_tool is not None, "Code interpreter tool is required"
|
||||
assert document_generator_tool is not None, (
|
||||
"Document generator tool is required"
|
||||
)
|
||||
self.tools = [
|
||||
self.query_engine_tool,
|
||||
self.code_interpreter_tool,
|
||||
self.document_generator_tool,
|
||||
]
|
||||
self.llm: FunctionCallingLLM = llm or Settings.llm
|
||||
assert isinstance(self.llm, FunctionCallingLLM)
|
||||
self.memory = ChatMemoryBuffer.from_defaults(llm=self.llm)
|
||||
|
||||
@step()
|
||||
async def prepare_chat_history(self, ctx: Context, ev: StartEvent) -> InputEvent:
|
||||
self.stream = ev.get("stream", True)
|
||||
user_msg = ev.get("user_msg")
|
||||
chat_history = ev.get("chat_history")
|
||||
|
||||
if chat_history is not None:
|
||||
self.memory.put_messages(chat_history)
|
||||
|
||||
# Add user message to memory
|
||||
self.memory.put(ChatMessage(role=MessageRole.USER, content=user_msg))
|
||||
|
||||
if self.system_prompt:
|
||||
system_msg = ChatMessage(
|
||||
role=MessageRole.SYSTEM, content=self.system_prompt
|
||||
)
|
||||
self.memory.put(system_msg)
|
||||
|
||||
return InputEvent(input=self.memory.get())
|
||||
|
||||
@step()
|
||||
async def handle_llm_input( # type: ignore
|
||||
self,
|
||||
ctx: Context,
|
||||
ev: InputEvent,
|
||||
) -> ResearchEvent | AnalyzeEvent | ReportEvent | StopEvent:
|
||||
"""
|
||||
Handle an LLM input and decide the next step.
|
||||
"""
|
||||
# Always use the latest chat history from the input
|
||||
chat_history: list[ChatMessage] = ev.input
|
||||
|
||||
# Get tool calls
|
||||
response = await chat_with_tools(
|
||||
self.llm,
|
||||
self.tools, # type: ignore
|
||||
chat_history,
|
||||
)
|
||||
if not response.has_tool_calls():
|
||||
if self.stream:
|
||||
return StopEvent(result=response.generator)
|
||||
else:
|
||||
return StopEvent(result=await response.full_response())
|
||||
# calling different tools at the same time is not supported at the moment
|
||||
# add an error message to tell the AI to process step by step
|
||||
if response.is_calling_different_tools():
|
||||
self.memory.put(
|
||||
ChatMessage(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content="Cannot call different tools at the same time. Try calling one tool at a time.",
|
||||
)
|
||||
)
|
||||
return InputEvent(input=self.memory.get())
|
||||
self.memory.put(response.tool_call_message)
|
||||
match response.tool_name():
|
||||
case self.code_interpreter_tool.metadata.name:
|
||||
return AnalyzeEvent(input=response.tool_calls)
|
||||
case self.document_generator_tool.metadata.name:
|
||||
return ReportEvent(input=response.tool_calls)
|
||||
case self.query_engine_tool.metadata.name:
|
||||
return ResearchEvent(input=response.tool_calls)
|
||||
case _:
|
||||
raise ValueError(f"Unknown tool: {response.tool_name()}")
|
||||
|
||||
@step()
|
||||
async def research(self, ctx: Context, ev: ResearchEvent) -> AnalyzeEvent:
|
||||
"""
|
||||
Do a research to gather information for the user's request.
|
||||
A researcher should have these tools: query engine, search engine, etc.
|
||||
"""
|
||||
ctx.write_event_to_stream(
|
||||
AgentRunEvent(
|
||||
name="Researcher",
|
||||
msg="Starting research",
|
||||
)
|
||||
)
|
||||
tool_calls = ev.input
|
||||
|
||||
tool_messages = await call_tools(
|
||||
ctx=ctx,
|
||||
agent_name="Researcher",
|
||||
tools=[self.query_engine_tool],
|
||||
tool_calls=tool_calls,
|
||||
)
|
||||
self.memory.put_messages(tool_messages)
|
||||
return AnalyzeEvent(
|
||||
input=ChatMessage(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content="I've finished the research. Please analyze the result.",
|
||||
),
|
||||
)
|
||||
|
||||
@step()
|
||||
async def analyze(self, ctx: Context, ev: AnalyzeEvent) -> InputEvent:
|
||||
"""
|
||||
Analyze the research result.
|
||||
"""
|
||||
ctx.write_event_to_stream(
|
||||
AgentRunEvent(
|
||||
name="Analyst",
|
||||
msg="Starting analysis",
|
||||
)
|
||||
)
|
||||
event_requested_by_workflow_llm = isinstance(ev.input, list)
|
||||
# Requested by the workflow LLM Input step, it's a tool call
|
||||
if event_requested_by_workflow_llm:
|
||||
# Set the tool calls
|
||||
tool_calls = ev.input
|
||||
else:
|
||||
# Otherwise, it's triggered by the research step
|
||||
# Use a custom prompt and independent memory for the analyst agent
|
||||
analysis_prompt = """
|
||||
You are a financial analyst, you are given a research result and a set of tools to help you.
|
||||
Always use the given information, don't make up anything yourself. If there is not enough information, you can asking for more information.
|
||||
If you have enough numerical information, it's good to include some charts/visualizations to the report so you can use the code interpreter tool to generate a report.
|
||||
"""
|
||||
# This is handled by analyst agent
|
||||
# Clone the shared memory to avoid conflicting with the workflow.
|
||||
chat_history = self.memory.get()
|
||||
chat_history.append(
|
||||
ChatMessage(role=MessageRole.SYSTEM, content=analysis_prompt)
|
||||
)
|
||||
chat_history.append(ev.input) # type: ignore
|
||||
# Check if the analyst agent needs to call tools
|
||||
response = await chat_with_tools(
|
||||
self.llm,
|
||||
[self.code_interpreter_tool],
|
||||
chat_history,
|
||||
)
|
||||
if not response.has_tool_calls():
|
||||
# If no tool call, fallback analyst message to the workflow
|
||||
analyst_msg = ChatMessage(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content=await response.full_response(),
|
||||
)
|
||||
self.memory.put(analyst_msg)
|
||||
return InputEvent(input=self.memory.get())
|
||||
else:
|
||||
# Set the tool calls and the tool call message to the memory
|
||||
tool_calls = response.tool_calls
|
||||
self.memory.put(response.tool_call_message)
|
||||
|
||||
# Call tools
|
||||
tool_messages = await call_tools(
|
||||
ctx=ctx,
|
||||
agent_name="Analyst",
|
||||
tools=[self.code_interpreter_tool],
|
||||
tool_calls=tool_calls, # type: ignore
|
||||
)
|
||||
self.memory.put_messages(tool_messages)
|
||||
|
||||
# Fallback to the input with the latest chat history
|
||||
return InputEvent(input=self.memory.get())
|
||||
|
||||
@step()
|
||||
async def report(self, ctx: Context, ev: ReportEvent) -> InputEvent:
|
||||
"""
|
||||
Generate a report based on the analysis result.
|
||||
"""
|
||||
ctx.write_event_to_stream(
|
||||
AgentRunEvent(
|
||||
name="Reporter",
|
||||
msg="Starting report generation",
|
||||
)
|
||||
)
|
||||
tool_calls = ev.input
|
||||
tool_messages = await call_tools(
|
||||
ctx=ctx,
|
||||
agent_name="Reporter",
|
||||
tools=[self.document_generator_tool],
|
||||
tool_calls=tool_calls,
|
||||
)
|
||||
self.memory.put_messages(tool_messages)
|
||||
|
||||
# After the tool calls, fallback to the input with the latest chat history
|
||||
return InputEvent(input=self.memory.get())
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
This is a [LlamaIndex](https://www.llamaindex.ai/) multi-agents project using [Workflows](https://docs.llamaindex.ai/en/stable/understanding/workflows/).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, setup the environment with poetry:
|
||||
|
||||
> **_Note:_** This step is not needed if you are using the dev-container.
|
||||
|
||||
```shell
|
||||
uv sync
|
||||
```
|
||||
|
||||
Then check the parameters that have been pre-configured in the `.env` file in this directory.
|
||||
Make sure you have the `OPENAI_API_KEY` set.
|
||||
|
||||
Second, run the development server:
|
||||
|
||||
```shell
|
||||
uv run dev
|
||||
```
|
||||
|
||||
## Use Case: Filling Financial CSV Template
|
||||
|
||||
To reproduce the use case, start the [frontend](../frontend/README.md) and follow these steps in the frontend:
|
||||
|
||||
1. Upload the Apple and Tesla financial reports from the [data](./data) directory. Just send an empty message.
|
||||
2. Upload the CSV file [sec_10k_template.csv](./sec_10k_template.csv) and send the message "Fill the missing cells in the CSV file".
|
||||
|
||||
The agent will fill the missing cells by retrieving the information from the uploaded financial reports and return a new CSV file with the filled cells.
|
||||
|
||||
### API endpoints
|
||||
|
||||
The example provides one streaming API endpoint `/api/chat`.
|
||||
You can test the endpoint with the following curl request:
|
||||
|
||||
```
|
||||
curl --location 'localhost:8000/api/chat' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "messages": [{ "role": "user", "content": "What can you do?" }] }'
|
||||
```
|
||||
|
||||
You can start editing the API by modifying `app/api/routers/chat.py` or `app/workflows/form_filling.py`. The API auto-updates as you save the files.
|
||||
|
||||
Open [http://localhost:8000](http://localhost:8000) with your browser to start the app.
|
||||
|
||||
To start the app optimized for **production**, run:
|
||||
|
||||
```
|
||||
uv run prod
|
||||
```
|
||||
|
||||
## Deployments
|
||||
|
||||
For production deployments, check the [DEPLOY.md](DEPLOY.md) file.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about LlamaIndex, take a look at the following resources:
|
||||
|
||||
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex.
|
||||
- [Workflows Introduction](https://docs.llamaindex.ai/en/stable/understanding/workflows/) - learn about LlamaIndex workflows.
|
||||
|
||||
You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome!
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
from .form_filling import create_workflow
|
||||
|
||||
__all__ = ["create_workflow"]
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from llama_index.core import Settings
|
||||
from llama_index.core.base.llms.types import ChatMessage, MessageRole
|
||||
from llama_index.core.llms.function_calling import FunctionCallingLLM
|
||||
from llama_index.core.memory import ChatMemoryBuffer
|
||||
from llama_index.core.tools import FunctionTool, QueryEngineTool, ToolSelection
|
||||
from llama_index.core.workflow import (
|
||||
Context,
|
||||
Event,
|
||||
StartEvent,
|
||||
StopEvent,
|
||||
Workflow,
|
||||
step,
|
||||
)
|
||||
|
||||
from app.engine.index import IndexConfig, get_index
|
||||
from app.engine.tools import ToolFactory
|
||||
from app.engine.tools.query_engine import get_query_engine_tool
|
||||
from app.workflows.events import AgentRunEvent
|
||||
from app.workflows.tools import (
|
||||
call_tools,
|
||||
chat_with_tools,
|
||||
)
|
||||
|
||||
|
||||
def create_workflow(
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
) -> Workflow:
|
||||
# Create query engine tool
|
||||
index_config = IndexConfig(**params)
|
||||
index = get_index(index_config)
|
||||
if index is None:
|
||||
query_engine_tool = None
|
||||
else:
|
||||
query_engine_tool = get_query_engine_tool(index=index)
|
||||
|
||||
configured_tools = ToolFactory.from_env(map_result=True)
|
||||
extractor_tool = configured_tools.get("extract_questions") # type: ignore
|
||||
filling_tool = configured_tools.get("fill_form") # type: ignore
|
||||
|
||||
workflow = FormFillingWorkflow(
|
||||
query_engine_tool=query_engine_tool,
|
||||
extractor_tool=extractor_tool, # type: ignore
|
||||
filling_tool=filling_tool, # type: ignore
|
||||
)
|
||||
|
||||
return workflow
|
||||
|
||||
|
||||
class InputEvent(Event):
|
||||
input: List[ChatMessage]
|
||||
response: bool = False
|
||||
|
||||
|
||||
class ExtractMissingCellsEvent(Event):
|
||||
tool_calls: list[ToolSelection]
|
||||
|
||||
|
||||
class FindAnswersEvent(Event):
|
||||
tool_calls: list[ToolSelection]
|
||||
|
||||
|
||||
class FillEvent(Event):
|
||||
tool_calls: list[ToolSelection]
|
||||
|
||||
|
||||
class FormFillingWorkflow(Workflow):
|
||||
"""
|
||||
A predefined workflow for filling missing cells in a CSV file.
|
||||
Required tools:
|
||||
- query_engine: A query engine to query for the answers to the questions.
|
||||
- extract_question: Extract missing cells in a CSV file and generate questions to fill them.
|
||||
- answer_question: Query for the answers to the questions.
|
||||
|
||||
Flow:
|
||||
1. Extract missing cells in a CSV file and generate questions to fill them.
|
||||
2. Query for the answers to the questions.
|
||||
3. Fill the missing cells with the answers.
|
||||
"""
|
||||
|
||||
_default_system_prompt = """
|
||||
You are a helpful assistant who helps fill missing cells in a CSV file.
|
||||
Only extract missing cells from CSV files.
|
||||
Only use provided data - never make up any information yourself. Fill N/A if an answer is not found.
|
||||
If there is no query engine tool or the gathered information has many N/A values indicating the questions don't match the data, respond with a warning and ask the user to upload a different file or connect to a knowledge base.
|
||||
"""
|
||||
stream: bool = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
query_engine_tool: Optional[QueryEngineTool],
|
||||
extractor_tool: FunctionTool,
|
||||
filling_tool: FunctionTool,
|
||||
llm: Optional[FunctionCallingLLM] = None,
|
||||
timeout: int = 360,
|
||||
system_prompt: Optional[str] = None,
|
||||
):
|
||||
super().__init__(timeout=timeout)
|
||||
self.system_prompt = system_prompt or self._default_system_prompt
|
||||
self.query_engine_tool = query_engine_tool
|
||||
self.extractor_tool = extractor_tool
|
||||
self.filling_tool = filling_tool
|
||||
if self.extractor_tool is None or self.filling_tool is None:
|
||||
raise ValueError("Extractor and filling tools are required.")
|
||||
self.tools = [self.extractor_tool, self.filling_tool]
|
||||
if self.query_engine_tool is not None:
|
||||
self.tools.append(self.query_engine_tool) # type: ignore
|
||||
self.llm: FunctionCallingLLM = llm or Settings.llm
|
||||
if not isinstance(self.llm, FunctionCallingLLM):
|
||||
raise ValueError("FormFillingWorkflow only supports FunctionCallingLLM.")
|
||||
self.memory = ChatMemoryBuffer.from_defaults(llm=self.llm)
|
||||
|
||||
@step()
|
||||
async def start(self, ctx: Context, ev: StartEvent) -> InputEvent:
|
||||
self.stream = ev.get("stream", True)
|
||||
user_msg = ev.get("user_msg", "")
|
||||
chat_history = ev.get("chat_history", [])
|
||||
|
||||
if chat_history:
|
||||
self.memory.put_messages(chat_history)
|
||||
|
||||
self.memory.put(ChatMessage(role=MessageRole.USER, content=user_msg))
|
||||
|
||||
if self.system_prompt:
|
||||
system_msg = ChatMessage(
|
||||
role=MessageRole.SYSTEM, content=self.system_prompt
|
||||
)
|
||||
self.memory.put(system_msg)
|
||||
|
||||
return InputEvent(input=self.memory.get())
|
||||
|
||||
@step()
|
||||
async def handle_llm_input( # type: ignore
|
||||
self,
|
||||
ctx: Context,
|
||||
ev: InputEvent,
|
||||
) -> ExtractMissingCellsEvent | FillEvent | StopEvent:
|
||||
"""
|
||||
Handle an LLM input and decide the next step.
|
||||
"""
|
||||
chat_history: list[ChatMessage] = ev.input
|
||||
response = await chat_with_tools(
|
||||
self.llm,
|
||||
self.tools,
|
||||
chat_history,
|
||||
)
|
||||
if not response.has_tool_calls():
|
||||
if self.stream:
|
||||
return StopEvent(result=response.generator)
|
||||
else:
|
||||
return StopEvent(result=await response.full_response())
|
||||
# calling different tools at the same time is not supported at the moment
|
||||
# add an error message to tell the AI to process step by step
|
||||
if response.is_calling_different_tools():
|
||||
self.memory.put(
|
||||
ChatMessage(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content="Cannot call different tools at the same time. Try calling one tool at a time.",
|
||||
)
|
||||
)
|
||||
return InputEvent(input=self.memory.get())
|
||||
self.memory.put(response.tool_call_message)
|
||||
match response.tool_name():
|
||||
case self.extractor_tool.metadata.name:
|
||||
return ExtractMissingCellsEvent(tool_calls=response.tool_calls)
|
||||
case self.query_engine_tool.metadata.name:
|
||||
return FindAnswersEvent(tool_calls=response.tool_calls)
|
||||
case self.filling_tool.metadata.name:
|
||||
return FillEvent(tool_calls=response.tool_calls)
|
||||
case _:
|
||||
raise ValueError(f"Unknown tool: {response.tool_name()}")
|
||||
|
||||
@step()
|
||||
async def extract_missing_cells(
|
||||
self, ctx: Context, ev: ExtractMissingCellsEvent
|
||||
) -> InputEvent | FindAnswersEvent:
|
||||
"""
|
||||
Extract missing cells in a CSV file and generate questions to fill them.
|
||||
"""
|
||||
ctx.write_event_to_stream(
|
||||
AgentRunEvent(
|
||||
name="Extractor",
|
||||
msg="Extracting missing cells",
|
||||
)
|
||||
)
|
||||
# Call the extract questions tool
|
||||
tool_messages = await call_tools(
|
||||
agent_name="Extractor",
|
||||
tools=[self.extractor_tool],
|
||||
ctx=ctx,
|
||||
tool_calls=ev.tool_calls,
|
||||
)
|
||||
self.memory.put_messages(tool_messages)
|
||||
return InputEvent(input=self.memory.get())
|
||||
|
||||
@step()
|
||||
async def find_answers(self, ctx: Context, ev: FindAnswersEvent) -> InputEvent:
|
||||
"""
|
||||
Call answer questions tool to query for the answers to the questions.
|
||||
"""
|
||||
ctx.write_event_to_stream(
|
||||
AgentRunEvent(
|
||||
name="Researcher",
|
||||
msg="Finding answers for missing cells",
|
||||
)
|
||||
)
|
||||
tool_messages = await call_tools(
|
||||
ctx=ctx,
|
||||
agent_name="Researcher",
|
||||
tools=[self.query_engine_tool],
|
||||
tool_calls=ev.tool_calls,
|
||||
)
|
||||
self.memory.put_messages(tool_messages)
|
||||
return InputEvent(input=self.memory.get())
|
||||
|
||||
@step()
|
||||
async def fill_cells(self, ctx: Context, ev: FillEvent) -> InputEvent:
|
||||
"""
|
||||
Call fill cells tool to fill the missing cells with the answers.
|
||||
"""
|
||||
ctx.write_event_to_stream(
|
||||
AgentRunEvent(
|
||||
name="Processor",
|
||||
msg="Filling missing cells",
|
||||
)
|
||||
)
|
||||
tool_messages = await call_tools(
|
||||
agent_name="Processor",
|
||||
tools=[self.filling_tool],
|
||||
ctx=ctx,
|
||||
tool_calls=ev.tool_calls,
|
||||
)
|
||||
self.memory.put_messages(tool_messages)
|
||||
return InputEvent(input=self.memory.get())
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user