Compare commits

..

73 Commits

Author SHA1 Message Date
github-actions[bot] 7711216134 Release 0.5.11 (#582)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-28 14:48:28 +07:00
Marcus Schiesser 93d601972e docs: fix llamaindexserver 2025-04-28 14:46:55 +07:00
Thuc Pham 8fe5fc24c1 chore: add llamaindex server package (#585) 2025-04-28 14:37:12 +07:00
Thuc Pham 3960618454 chore: create-llama monorepo (#581)
* chore: create-llama monorepo

* add root package.json and pnpm workspace

* keep e2e inside create-llama

* update root package.json

* move scripts and dev dependencies of create-llama to root

* update e2e test for create-llama package

* update lint workflow

* update release llama-index-server workflow

* update path for test_llama_index_server workflow

* remove local lock file

* keep lint and format in create-llama

* fix: format

* update pre-commit

* move playwright back to create-llama

* disable pnpm for installing generated frontend

* use npm for type check

* update gitignore

* try --ignore-workspace option

* Move llama-index-server from packages/python-server to python directory

* update CI for python server

* Create plenty-spies-tickle.md
2025-04-25 18:38:02 +07:00
github-actions[bot] 53e1cd56e7 Release 0.5.10 (#579)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-22 15:45:31 +07:00
Huu Le 0a2e12a2bb Use uv as the default package manager and deprecate poetry. (#578) 2025-04-22 15:44:11 +07:00
github-actions[bot] 2e536dca36 Release 0.5.9 (#577)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-18 19:35:10 +07:00
Huu Le 4bc53ac24e feat: support UI generator for TS (#566) 2025-04-18 19:14:29 +07:00
github-actions[bot] 2deb63a6cc chore(release): bump version to 0.1.14 (#567)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-18 17:54:50 +07:00
github-actions[bot] 2ffa057f77 Release 0.5.8 (#573)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-18 17:51:19 +07:00
Huu Le 64f151dd66 bump chat ui (#575) 2025-04-18 17:43:22 +07:00
Thuc Pham 765181adb0 chore: test typescript e2e with node 20 and 22 (#572)
* chore: test typescript e2e with node 20 and 22

* Create sixty-chefs-search.md
2025-04-17 10:06:35 +02:00
github-actions[bot] 95c35e8a5c Release 0.5.7 (#571)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-17 13:51:52 +07:00
Thuc Pham 598865768a chore: bump llmaindex (#570) 2025-04-17 13:49:53 +07:00
github-actions[bot] 05453d55bf Release 0.5.6 (#569)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-16 20:40:15 +07:00
Huu Le d363ced4d8 bump llamaindex server package versions to 0.1.13 (python) and 0.1.3 (ts) (#568) 2025-04-16 20:38:58 +07:00
github-actions[bot] 293c6f97c1 chore(release): bump version to 0.1.13 (#561)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-16 16:29:41 +07:00
Huu Le 44b4d89ac1 Update document link and fix import (#565) 2025-04-16 16:23:17 +07:00
github-actions[bot] 60f10c5b5d Release 0.5.5 (#564)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-15 20:55:53 +07:00
Huu Le ee85320701 fix: missing default export (#563) 2025-04-15 20:54:23 +07:00
github-actions[bot] b12dc6f1e8 Release 0.5.4 (#562)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-15 18:28:11 +07:00
Huu Le 7c3b279417 support code generation of event components using an LLM (Python) (#557)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-04-15 18:23:06 +07:00
github-actions[bot] 1514a555d5 chore(release): bump version to 0.1.12 (#559)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-15 17:32:13 +07:00
Huu Le cddb4f6bcc chore: bump chat UI version to 0.1.2 and rename generate_ui_for_workflow (#560)
* chore: bump chat UI version to 0.1.2 and rename generate_ui_for_workflow

* feat: add exports for event component generation in gen_ui module

* update document

* refine prompt
2025-04-15 17:27:22 +07:00
github-actions[bot] c82e4f5791 chore(release): bump version to 0.1.11 (#555)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-15 13:11:15 +07:00
Huu Le 1f7e0e3c69 add GenUIWorkflow for generating UI components from workflow events (#549)
* feat: add GenUIWorkflow for generating UI components from workflow events

* feat: enhance GenUIWorkflow to support event handling and UI generation

* add cache, split code

* use gemini model

* refactor: update GenUIWorkflow to use Anthropic model and add pre-run checks for API key and package installation

* feat: introduce PlanningEvent and enhance GenUIWorkflow for improved UI planning and aggregation function generation

* feat: add gen ui to llamaindexserver

* refactor: remove unused gen_ui.py file

* simplify

* update for tailwindcss

* simplify code and add document

* refine text

* feat: add UIEvent model and update exports in server module

* use default UIEvent

* fix wrong model, update template

* add missing doc

* fix linting

* revert change on template

* fix mypy

* disable e2e for the change from llama-index-server

* remove unused script entry from pyproject.toml and refine UI notice text in GenUIWorkflow

* update workflow, bump chat ui

* Refine GenUIWorkflow documentation and improve code structure notes; add llm parameter to generate_ui_for_workflow function.
2025-04-15 13:06:55 +07:00
github-actions[bot] 7997cdeb70 Release 0.5.3 (#556)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 19:08:02 +07:00
Huu Le 76ec3605e5 update templates to use new chat UI config (#553) 2025-04-10 19:03:06 +07:00
github-actions[bot] 5cfdec7d75 chore(release): bump version to 0.1.10 (#550)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-10 17:47:23 +07:00
Huu Le 3d1b15d515 fix encoding windows (#554) 2025-04-10 17:37:49 +07:00
Huu Le 392393af9e feat: Add config app title for python, enhance config parameter. (#540)
* Enhance LlamaIndexServer UI configuration

* bump version, add use llamacloud to chat ui config

* add changeset

* refactor: streamline UI configuration and component directory handling

* relock and fix test

* remove change set

* update docs

* fix wrong key name

* fix test

* bump chat ui

* improve docs
2025-04-10 16:45:20 +07:00
Marcus Schiesser 920beda8ad chore: use own DeepResearchEvent (#552) 2025-04-09 20:44:38 +07:00
github-actions[bot] e6f8add778 Release 0.5.2 (#551)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-09 19:40:36 +07:00
Huu Le c9f8f8d5f2 feat: Use custom component for deep research use case (#548) 2025-04-09 19:31:09 +07:00
github-actions[bot] 24eb7736ee chore(release): bump version to 0.1.9 (#545)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-09 19:01:03 +07:00
Huu Le 5fb27220f7 feat: Add componentDir for llama_index_sever (#547)
* init code for custom components

* change router name

* use jsx

* add custom components code

* revert change on create-llama

* fix mypy

* adding document for custom component

* Refactor component directory handling in LlamaIndexServer

* add file name in components response

* Enhance documentation

* fix mypy

* use tmp in test

* docs: word smithing

* Refactor component loading logic in CustomUI to prioritize TSX over JSX files and improve duplicate handling.

* bump chat ui

---------

Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-04-09 18:51:39 +07:00
github-actions[bot] 5caa3813f8 chore(release): bump version to 0.1.8 (#534)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-03 21:33:54 +07:00
github-actions[bot] bc95789a8d Release 0.5.1 (#544)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-03 15:25:09 +02:00
Huu Le 08b3e079e4 chore: simplify local index code (#537) 2025-04-03 14:21:50 +02:00
Huu Le 1876950f89 fix null embedding model name when create llamacloud index (#543) 2025-04-03 13:10:19 +02:00
ForgQi c7349b44c4 fix: bump llama-index-core to fix handle missing fields parameter in default_formatter (#542)
* fix: handle missing fields parameter in default_formatter to avoid runtime error

https://github.com/run-llama/llama_index/pull/18340

* relock packages

---------

Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2025-04-03 16:35:51 +07:00
github-actions[bot] 4068618b2d Release 0.5.0 (#508)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-02 19:34:57 +07:00
Huu Le 54c9e2f95e Feature: Simplify app code using LlamaIndexServer (#529)
---------
Co-authored-by: thucpn <thucsh2@gmail.com>
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-04-02 19:31:06 +07:00
github-actions[bot] aec1173b71 chore(release): bump version to 0.1.7 (#531)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-02 17:50:01 +07:00
Huu Le 481663dd63 chore(release): bump CHAT_UI_VERSION to 0.0.6 (#533) 2025-04-02 17:35:58 +07:00
Huu Le 1ca7dd2e48 fix llamacloud api and markdown issue (#532) 2025-04-02 17:07:25 +07:00
github-actions[bot] 3d20990713 chore(release): bump version to 0.1.6 (#528)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-01 22:25:51 +07:00
Huu Le 8fb69cf807 feat: add llamacloud to llama_index_server (#530) 2025-04-01 22:23:34 +07:00
github-actions[bot] 61af56dac6 chore(release): bump version to 0.1.5 (#526)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 22:41:43 +07:00
Huu Le 4b66039a96 update variable (#527) 2025-03-26 22:40:34 +07:00
github-actions[bot] ee88f681a6 chore(release): bump version to 0.1.4 (#524)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 17:55:02 +07:00
Huu Le 992c3a95e9 update release workflow for llama-index-server (#525) 2025-03-26 17:53:33 +07:00
github-actions[bot] 2a4fb702d1 chore(release): bump version to 0.1.3 (#522)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 17:39:28 +07:00
Huu Le 24b9337096 fix: poetry release ci (#523)
* Fix unnecessary create PR and wrong PyPI environment name

* use JRubics/poetry-publish
2025-03-26 17:36:53 +07:00
github-actions[bot] fceec69a3a chore(release): release llama-index-server 0.1.2 (#520)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 17:10:54 +07:00
Huu Le 03e5e0a16e fix release ci, add --no-interaction (#521) 2025-03-26 17:09:16 +07:00
github-actions[bot] fe3cd36d3a chore(release): bump version to 0.1.1 (#517)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-26 16:55:29 +07:00
Huu Le d5d10e9ead Support overriding UI configuration for LlamaIndexServer (#519)
* support for ui config override

* remove dead code

* bump chat ui

* fix linting
2025-03-26 16:39:27 +07:00
Huu Le 5ed925d75f stream ToolCallResult event in agent tool utils (#518) 2025-03-26 13:38:50 +07:00
Huu Le ca5df14d41 feat: Add llama_index_sever (#516) 2025-03-25 20:59:52 +07:00
Thuc Pham ee69ce7cc1 bump: chat-ui and tailwind v4 (#509) 2025-02-25 09:38:31 +07:00
Thuc Pham 0e4ecfaf8b fix: add trycatch for generating error (#507) 2025-02-20 16:34:14 +07:00
github-actions[bot] 3658fec684 Release 0.4.0 (#499)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-20 11:11:09 +07:00
Marcus Schiesser c3d275abe1 make minor release 2025-02-20 11:07:56 +07:00
Thuc Pham 61204a1381 chore: bump LITS 0.9 (#505)
---------
Co-authored-by: leehuwuj <leehuwuj@gmail.com>
2025-02-20 10:33:22 +07:00
Huu Le 9e723c3a15 Standardize the code of workflow use cases (#495) 2025-02-05 11:10:47 +07:00
Thuc Pham d5da55b993 feat: add components.json to use CLI (#501) 2025-02-05 11:04:16 +07:00
Thuc Pham c1552ebb00 chore: move wikipedia tool to create-llama (#498) 2025-02-03 17:35:19 +07:00
github-actions[bot] 131e63ae4a Release 0.3.28 (#494)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-22 17:37:12 +07:00
Huu Le 4e06714cdd Fix: deep research use case (#493) 2025-01-22 17:24:12 +07:00
Ravi Kumar 18c8d2540c added EMBEDDING_DIM if available or undefined to fallback to default config (#487) 2025-01-22 12:00:26 +07:00
github-actions[bot] d4b4338f54 Release 0.3.27 (#492)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-22 10:59:19 +07:00
Huu Le b4e41aa526 feat: Add deep research use case (Python) (#482)
---------
Co-authored-by: Marcus Schiesser <mail@marcusschiesser.de>
2025-01-22 10:22:49 +07:00
575 changed files with 34909 additions and 2561 deletions
+33 -28
View File
@@ -1,12 +1,13 @@
name: E2E Tests
name: E2E Tests for create-llama package
on:
push:
branches: [main]
paths-ignore:
- "python/llama-index-server/**"
pull_request:
branches: [main]
env:
POETRY_VERSION: "1.6.1"
paths-ignore:
- "python/llama-index-server/**"
jobs:
e2e-python:
@@ -32,10 +33,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 +51,15 @@ 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: Run Playwright tests for Python
run: pnpm run e2e:python
@@ -67,13 +68,16 @@ jobs:
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
FRAMEWORK: ${{ matrix.frameworks }}
DATASOURCE: ${{ matrix.datasources }}
working-directory: .
PYTHONIOENCODING: utf-8
PYTHONLEGACYWINDOWSSTDIO: utf-8
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 }}
path: packages/create-llama/playwright-report/
overwrite: true
retention-days: 30
e2e-typescript:
@@ -82,10 +86,10 @@ 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"]
frameworks: ["nextjs"]
datasources: ["--no-files", "--example-file", "--llamacloud"]
defaults:
run:
@@ -99,10 +103,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 +121,15 @@ 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: Run Playwright tests for TypeScript
run: pnpm run e2e:typescript
@@ -134,11 +138,12 @@ jobs:
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
FRAMEWORK: ${{ matrix.frameworks }}
DATASOURCE: ${{ matrix.datasources }}
working-directory: .
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 }}
path: packages/create-llama/playwright-report/
overwrite: true
retention-days: 30
@@ -35,8 +35,10 @@ jobs:
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"
@@ -0,0 +1,130 @@
name: Release llama-index-server
on:
push:
branches:
- main
paths:
- "python/llama-index-server/**"
- ".github/workflows/release_llama_index_server.yml"
pull_request:
types:
- closed
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
release:
name: Create Release PR
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./python/llama-index-server
if: |
github.event_name == 'push' &&
!startsWith(github.ref, 'refs/heads/release/llama-index-server-v')
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
- name: Install dependencies
run: poetry install
- name: Setup Git
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
- name: Bump patch version
run: |
poetry version patch
git add pyproject.toml
git commit -m "chore(release): bump version to $(poetry version -s)"
- name: Get current version
id: get_version
run: |
version=$(poetry version -s)
echo "current_version=${version}" >> "$GITHUB_OUTPUT"
- name: Create Release PR
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Release: llama-index-server v${{ steps.get_version.outputs.current_version }}"
title: "Release: llama-index-server v${{ steps.get_version.outputs.current_version }}"
body: |
This PR was automatically created to release a new version of the llama-index-server package.
Version: ${{ steps.get_version.outputs.current_version }}
Please review the changes and merge to trigger the release.
branch: release/llama-index-server-v${{ steps.get_version.outputs.current_version }}
base: main
labels: release, llama-index-server
publish:
name: Publish to PyPI
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./python/llama-index-server
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
startsWith(github.event.pull_request.title, 'Release: llama-index-server') &&
startsWith(github.event.pull_request.head.ref, 'release/llama-index-server-v')
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
- name: Install dependencies
run: poetry install
- name: Get current version
id: get_version
run: |
version=$(poetry version -s)
echo "current_version=${version}" >> "$GITHUB_OUTPUT"
- name: Build and publish to PyPI
uses: JRubics/poetry-publish@v2.1
with:
python_version: "3.11"
pypi_token: ${{ secrets.PYPI_TOKEN }}
package_directory: "python/llama-index-server"
poetry_install_options: "--without dev"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: llama-index-server-v${{ steps.get_version.outputs.current_version }}
name: "llama-index-server v${{ steps.get_version.outputs.current_version }}"
body: |
Release of llama-index-server v${{ steps.get_version.outputs.current_version }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,111 @@
name: Build Package
on:
pull_request:
env:
POETRY_VERSION: "1.8.3"
PYTHON_VERSION: "3.9"
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
- name: Install Poetry
run: pipx install poetry==${{ env.POETRY_VERSION }}
- name: Set up python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
- name: Configure Poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
poetry env use python
- name: Install dependencies
shell: bash
run: poetry install --with dev
- name: Run unit tests
shell: bash
run: |
poetry 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
- name: Install Poetry
run: pipx install poetry==${{ env.POETRY_VERSION }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: "poetry"
- name: Configure Poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
poetry env use python
- name: Install dependencies
shell: bash
run: poetry install --with dev
- name: Run mypy
shell: bash
run: poetry 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
- name: Install Poetry
run: pipx install poetry==${{ env.POETRY_VERSION }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Clear python cache
shell: bash
run: poetry cache clear --all pypi
- name: Build package
shell: bash
run: poetry build
- name: Test installing built package
shell: bash
run: python -m pip install .
- name: Test import
shell: bash
working-directory: ${{ vars.RUNNER_TEMP }}
run: python -c "from llama_index.server import LlamaIndexServer"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: llama-index-server
path: python/llama-index-server/dist/
-18
View File
@@ -6,9 +6,6 @@ node_modules
.pnpm-store
.pnp.js
# testing
coverage
# next.js
.next/
out/
@@ -34,24 +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
+1 -1
View File
@@ -1,3 +1,3 @@
pnpm format
pnpm lint
uvx ruff format --check templates/
uvx ruff format --check packages/create-llama/templates/
+8 -1
View File
@@ -55,7 +55,7 @@ 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
```
## Customizing the AI models
@@ -130,4 +130,11 @@ Pro mode is ideal for developers who want fine-grained control over their projec
- [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)
+37 -84
View File
@@ -1,86 +1,39 @@
{
"name": "create-llama",
"version": "0.3.26",
"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"
],
"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",
"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"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"@playwright/test": "^1.41.1",
"@vercel/ncc": "0.38.1",
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.10.0",
"husky": "^9.0.10",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"rimraf": "^5.0.5",
"typescript": "^5.3.3",
"wait-port": "^1.1.0"
},
"packageManager": "pnpm@9.0.5",
"engines": {
"node": ">=16.14.0"
}
"name": "create-llama-monorepo",
"version": "1.0.0",
"private": true,
"description": "Monorepo for create-llama",
"keywords": [
"rag",
"llamaindex"
],
"repository": {
"type": "git",
"url": "https://github.com/run-llama/create-llama"
},
"license": "MIT",
"workspaces": [
"packages/*"
],
"scripts": {
"prepare": "husky",
"new-snapshot": "pnpm -r build && changeset version --snapshot",
"new-version": "pnpm -r build && changeset version",
"release": "pnpm -r build && changeset publish",
"release-snapshot": "pnpm -r build && changeset publish --tag snapshot",
"build": "pnpm -r build",
"e2e": "pnpm -r e2e",
"dev": "pnpm -r dev",
"format": "pnpm -r format",
"format:write": "pnpm -r format:write",
"lint": "pnpm -r lint"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"husky": "^9.0.10"
},
"packageManager": "pnpm@9.0.5",
"engines": {
"node": ">=16.14.0"
}
}
+65
View File
@@ -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,108 @@
# create-llama
## 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
@@ -90,7 +90,7 @@ export async function createApp({
// Install backend
await installTemplate({ ...args, backend: true });
if (frontend && framework === "fastapi") {
if (frontend && framework === "fastapi" && template !== "llamaindexserver") {
// install frontend
const frontendRoot = path.join(root, ".frontend");
await makeDir(frontendRoot);
@@ -110,7 +110,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(
@@ -195,32 +195,47 @@ async function createAndCheckLlamaProject({
const pyprojectPath = path.join(projectPath, "pyproject.toml");
expect(fs.existsSync(pyprojectPath)).toBeTruthy();
const env = {
// Modify environment for the command
const commandEnv = {
...process.env,
POETRY_VIRTUALENVS_IN_PROJECT: "true",
};
// Run poetry install
console.log("Running uv venv...");
try {
const { stdout: installStdout, stderr: installStderr } = await execAsync(
"poetry install",
{ cwd: projectPath, env },
const { stdout: venvStdout, stderr: venvStderr } = await execAsync(
"uv venv",
{ cwd: projectPath, env: commandEnv },
);
console.log("poetry install stdout:", installStdout);
console.error("poetry install stderr:", installStderr);
console.log("uv venv stdout:", venvStdout);
console.error("uv venv stderr:", venvStderr);
} catch (error) {
console.error("Error running poetry install:", error);
throw error;
console.error("Error running uv venv:", error);
throw error; // Re-throw error to fail the test
}
// Run poetry run mypy
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(
"poetry run mypy .",
{ cwd: projectPath, env },
"uv run mypy .",
{ cwd: projectPath, env: commandEnv },
);
console.log("poetry run mypy stdout:", mypyStdout);
console.error("poetry run mypy stderr:", mypyStderr);
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;
@@ -16,15 +16,17 @@ const templateFramework: TemplateFramework = process.env.FRAMEWORK
const dataSource: string = "--example-file";
const templateUI: TemplateUI = "shadcn";
const templatePostInstallAction: TemplatePostInstallAction = "runApp";
const appType: AppType = templateFramework === "fastapi" ? "--frontend" : "";
const appType: AppType = "--frontend";
const userMessage = "Write a blog post about physical standards for letters";
const templateUseCases = ["financial_report", "blog", "form_filling"];
const templateUseCases = ["financial_report", "agentic_rag", "deep_research"];
for (const useCase of templateUseCases) {
test.describe(`Test multiagent template ${useCase} ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
test.describe(`Test use case ${useCase} ${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.",
process.platform !== "linux" ||
process.env.DATASOURCE === "--no-files" ||
templateFramework === "express",
"The llamaindexserver template currently only works with nextjs, fastapi. We also only run on Linux to speed up tests.",
);
let port: number;
let cwd: string;
@@ -38,7 +40,7 @@ for (const useCase of templateUseCases) {
cwd = await createTestDir();
const result = await runCreateLlama({
cwd,
templateType: "multiagent",
templateType: "llamaindexserver",
templateFramework,
dataSource,
vectorDb,
@@ -63,7 +65,9 @@ for (const useCase of templateUseCases) {
templateFramework === "express",
);
await page.goto(`http://localhost:${port}`);
await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
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 ({
@@ -72,9 +76,9 @@ for (const useCase of templateUseCases) {
test.skip(
templatePostInstallAction !== "runApp" ||
useCase === "financial_report" ||
useCase === "form_filling" ||
useCase === "deep_research" ||
templateFramework === "express",
"Skip chat tests for financial report and form filling.",
"Skip chat tests for financial report and deep research.",
);
await page.goto(`http://localhost:${port}`);
await page.fill("form textarea", userMessage);
@@ -86,6 +90,12 @@ for (const useCase of templateUseCases) {
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();
});
@@ -74,7 +74,7 @@ test.describe("Test resolve TS dependencies", () => {
// Install dependencies using pnpm
try {
const { stderr: installStderr } = await execAsync(
"pnpm install --prefer-offline",
"pnpm install --prefer-offline --ignore-workspace",
{
cwd: appDir,
},
@@ -113,7 +113,12 @@ export async function runCreateLlama({
if (observability) {
commandArgs.push("--observability", observability);
}
if ((templateType === "multiagent" || templateType === "reflex") && useCase) {
if (
(templateType === "multiagent" ||
templateType === "reflex" ||
templateType === "llamaindexserver") &&
useCase
) {
commandArgs.push("--use-case", useCase);
}
@@ -42,6 +42,16 @@ export const EXAMPLE_GDPR: TemplateDataSource = {
},
};
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,
@@ -44,6 +44,7 @@ const renderEnvVar = (envVars: EnvVar[]): string => {
const getVectorDBEnvs = (
vectorDb?: TemplateVectorDB,
framework?: TemplateFramework,
template?: TemplateType,
): EnvVar[] => {
if (!vectorDb || !framework) {
return [];
@@ -168,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)
[
{
@@ -223,13 +224,15 @@ Otherwise, use CHROMA_HOST and CHROMA_PORT config above`,
},
];
default:
return [
{
name: "STORAGE_CACHE_DIR",
description: "The directory to store the local storage cache.",
value: ".cache",
},
];
return template !== "llamaindexserver"
? [
{
name: "STORAGE_CACHE_DIR",
description: "The directory to store the local storage cache.",
value: ".cache",
},
]
: [];
}
};
@@ -382,38 +385,42 @@ 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") {
if (framework === "nextjs" && template !== "llamaindexserver") {
result.push({
name: "NEXT_PUBLIC_CHAT_API",
description:
@@ -569,25 +576,41 @@ export const createBackendEnvFile = async (
| "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.port),
...(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);
@@ -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";
@@ -22,6 +21,7 @@ import {
TemplateVectorDB,
} from "./types";
import { installTSTemplate } from "./typescript";
import { isHavingUvLockFile, tryUvRun } from "./uv";
const checkForGenerateScript = (
modelConfig: ModelConfig,
@@ -41,7 +41,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");
}
@@ -60,7 +64,7 @@ async function generateContextData(
if (packageManager) {
const runGenerate = `${cyan(
framework === "fastapi"
? "poetry run generate"
? "uv run generate"
: `${packageManager} run generate`,
)}`;
@@ -74,15 +78,21 @@ 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.`);
@@ -92,14 +102,14 @@ async function generateContextData(
}
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, Buffer.from(fileBuffer));
await fsExtra.writeFile(destPath, new Uint8Array(fileBuffer));
};
const prepareContextData = async (
@@ -166,6 +176,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
@@ -175,26 +196,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 === "reflex"
) {
if (props.template !== "community" && props.template !== "llamapack") {
await createBackendEnvFile(props.root, props);
}
@@ -143,6 +143,6 @@ export const installLlamapackProject = async ({
await copyData({ root });
await installLlamapackExample({ root, llamapack });
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
installPythonDependencies({ noRoot: true });
installPythonDependencies();
}
};
@@ -3,15 +3,16 @@ 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 { 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";
@@ -29,6 +30,7 @@ const getAdditionalDependencies = (
dataSources?: TemplateDataSource[],
tools?: Tool[],
templateType?: TemplateType,
observability?: TemplateObservability,
) => {
const dependencies: Dependency[] = [];
@@ -37,21 +39,21 @@ const getAdditionalDependencies = (
case "mongo": {
dependencies.push({
name: "llama-index-vector-stores-mongodb",
version: "^0.6.0",
version: ">=0.3.2,<0.4.0",
});
break;
}
case "pg": {
dependencies.push({
name: "llama-index-vector-stores-postgres",
version: "^0.3.2",
version: ">=0.3.2,<0.4.0",
});
break;
}
case "pinecone": {
dependencies.push({
name: "llama-index-vector-stores-pinecone",
version: "^0.4.1",
version: ">=0.4.1,<0.5.0",
constraints: {
python: ">=3.11,<3.13",
},
@@ -61,25 +63,25 @@ const getAdditionalDependencies = (
case "milvus": {
dependencies.push({
name: "llama-index-vector-stores-milvus",
version: "^0.3.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.4.0",
version: ">=0.4.0,<0.5.0",
});
break;
}
case "qdrant": {
dependencies.push({
name: "llama-index-vector-stores-qdrant",
version: "^0.4.0",
version: ">=0.4.0,<0.5.0",
constraints: {
python: ">=3.11,<3.13",
},
@@ -89,21 +91,21 @@ const getAdditionalDependencies = (
case "chroma": {
dependencies.push({
name: "llama-index-vector-stores-chroma",
version: "^0.4.0",
version: ">=0.4.0,<0.5.0",
});
break;
}
case "weaviate": {
dependencies.push({
name: "llama-index-vector-stores-weaviate",
version: "^1.2.3",
version: ">=1.2.3,<2.0.0",
});
break;
}
case "llamacloud":
dependencies.push({
name: "llama-index-indices-managed-llama-cloud",
version: "^0.6.3",
version: ">=0.6.3,<0.7.0",
});
break;
}
@@ -116,28 +118,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.3.0",
version: ">=0.3.0,<0.4.0",
});
break;
case "db":
dependencies.push({
name: "llama-index-readers-database",
version: "^0.3.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;
}
@@ -156,155 +158,122 @@ 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.3.2",
version: ">=0.3.2,<0.4.0",
});
dependencies.push({
name: "llama-index-embeddings-openai",
version: "^0.3.1",
version: ">=0.3.1,<0.4.0",
});
dependencies.push({
name: "llama-index-agent-openai",
version: "^0.4.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.3.5",
version: ">=0.5.0,<0.6.0",
});
dependencies.push({
name: "llama-index-embeddings-huggingface",
version: "^0.3.1",
version: ">=0.5.0,<0.6.0",
});
dependencies.push({
name: "optimum",
version: "^1.23.3",
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,<0.16.0",
});
}
value.version = dependency.version ?? value.version;
value.extras = dependency.extras ?? value.extras;
// Merge constraints if they exist
if (dependency.constraints) {
value = { ...value, ...dependency.constraints };
}
if (value.version === undefined) {
throw new Error(
`Dependency "${dependency.name}" is missing attribute "version"!`,
);
}
// Serialize as object if there are any additional properties
if (Object.keys(value).length > 1) {
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",
});
}
}
return dependencies;
};
const copyRouterCode = async (root: string, tools: Tool[]) => {
@@ -329,19 +298,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`,
@@ -350,18 +400,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);
@@ -369,57 +417,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 ({
appName,
const installLegacyPythonTemplate = async ({
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", "streaming", framework);
}
await copy("**", root, {
parents: true,
cwd: templatePath,
rename: assetRelocator,
});
const compPath = path.join(templatesDir, "components");
const enginePath = path.join(root, "app", "engine");
@@ -509,34 +534,7 @@ export const installPythonTemplate = async ({
}
}
console.log("Adding additional dependencies");
const addOnDependencies = getAdditionalDependencies(
modelConfig,
vectorDb,
dataSources,
tools,
template,
);
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.3.0",
constraints: {
python: ">=3.11,<3.13",
},
});
}
const templateObservabilityPath = path.join(
templatesDir,
"components",
@@ -548,6 +546,133 @@ 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("workflow.py", path.join(root, "app"), {
parents: true,
cwd: path.join(templatesDir, "components", "workflows", "python", useCase),
});
// Copy custom UI component code
await copy(`*`, path.join(root, "components"), {
parents: true,
cwd: path.join(templatesDir, "components", "ui", "workflows", useCase),
});
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", "workflows", "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,
);
await addDependencies(root, addOnDependencies);
@@ -34,14 +34,24 @@ export function runReflexApp(appPath: string, port: number) {
"--frontend-port",
port.toString(),
];
return createProcess("poetry", commandArgs, {
return createProcess("uv", commandArgs, {
stdio: "inherit",
cwd: appPath,
});
}
export function runFastAPIApp(appPath: string, port: number) {
return createProcess("poetry", ["run", "dev"], {
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}` },
@@ -73,7 +83,7 @@ export async function runApp(
: framework === "fastapi"
? runFastAPIApp
: runTSApp;
await appRunner(appPath, port || defaultPort);
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.3.0",
version: ">=0.3.0,<0.4.0",
},
],
supportedFrameworks: ["fastapi"],
@@ -62,7 +62,7 @@ export const supportedTools: Tool[] = [
dependencies: [
{
name: "duckduckgo-search",
version: "^6.3.5",
version: ">=6.3.5,<7.0.0",
},
],
supportedFrameworks: ["fastapi"], // TODO: Re-enable this tool once the duck-duck-scrape TypeScript library works again
@@ -82,7 +82,7 @@ For better results, you can specify the region parameter to get results from a s
dependencies: [
{
name: "llama-index-tools-wikipedia",
version: "^0.3.0",
version: ">=0.3.0,<0.4.0",
},
],
supportedFrameworks: ["fastapi", "express", "nextjs"],
@@ -102,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,
@@ -124,7 +124,7 @@ For better results, you can specify the region parameter to get results from a s
dependencies: [
{
name: "e2b_code_interpreter",
version: "1.0.3",
version: ">=1.1.1,<1.2.0",
},
],
supportedFrameworks: ["fastapi", "express", "nextjs"],
@@ -155,7 +155,7 @@ For better results, you can specify the region parameter to get results from a s
dependencies: [
{
name: "e2b_code_interpreter",
version: "1.0.3",
version: ">=1.1.1,<1.2.0",
},
],
supportedFrameworks: ["fastapi", "express", "nextjs"],
@@ -184,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",
@@ -247,11 +247,11 @@ For better results, you can specify the region parameter to get results from a s
dependencies: [
{
name: "pandas",
version: "^2.2.3",
version: ">=2.2.3,<3.0.0",
},
{
name: "tabulate",
version: "^0.9.0",
version: ">=0.9.0,<1.0.0",
},
],
},
@@ -325,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),
);
}
};
@@ -24,7 +24,8 @@ export type TemplateType =
| "community"
| "llamapack"
| "multiagent"
| "reflex";
| "reflex"
| "llamaindexserver";
export type TemplateFramework = "nextjs" | "express" | "fastapi";
export type TemplateUI = "html" | "shadcn";
export type TemplateVectorDB =
@@ -52,9 +53,11 @@ export type TemplateObservability = "none" | "traceloop" | "llamatrace";
export type TemplateUseCase =
| "financial_report"
| "blog"
| "deep_research"
| "form_filling"
| "extractor"
| "contract_review";
| "contract_review"
| "agentic_rag";
// Config for both file and folder
export type FileSourceConfig =
| {
@@ -6,43 +6,106 @@ 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("workflow.ts", path.join(root, "src", "app"), {
parents: true,
cwd: path.join(
templatesDir,
"components",
"workflows",
"typescript",
useCase,
),
});
// copy workflow UI components to output/components folder
await copy("*", path.join(root, "components"), {
parents: true,
cwd: path.join(templatesDir, "components", "ui", "workflows", useCase),
});
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",
});
}
// Copy README.md
await copy("README-template.md", path.join(root), {
parents: true,
cwd: path.join(
templatesDir,
"components",
"workflows",
"typescript",
useCase,
),
rename: assetRelocator,
});
};
const installLegacyTSTemplate = async ({
root,
packageManager,
isOnline,
template,
backend,
framework,
ui,
vectorDb,
postInstallAction,
backend,
observability,
tools,
dataSources,
useLlamaParse,
useCase,
}: 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,
});
modelConfig,
relativeEngineDestPath,
}: InstallTemplateArgs & {
backend: boolean;
relativeEngineDestPath: string;
}) => {
/**
* If next.js is used, update its configuration if necessary
*/
@@ -97,10 +160,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
@@ -181,6 +240,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 ?? [];
@@ -229,6 +294,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,
@@ -239,6 +373,9 @@ export const installTSTemplate = async ({
ui,
observability,
vectorDb,
backend,
modelConfig,
template,
});
if (
@@ -249,6 +386,68 @@ export const installTSTemplate = async ({
}
};
const providerDependencies: {
[key in ModelProvider]?: Record<string, string>;
} = {
openai: {
"@llamaindex/openai": "^0.2.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({
root,
appName,
@@ -258,6 +457,9 @@ async function updatePackageJson({
ui,
observability,
vectorDb,
backend,
modelConfig,
template,
}: Pick<
InstallTemplateArgs,
| "root"
@@ -267,8 +469,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(
@@ -277,7 +482,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 = {
@@ -308,32 +513,25 @@ async function updatePackageJson({
};
}
if (vectorDb === "pg") {
if (backend) {
packageJson.dependencies = {
...packageJson.dependencies,
pg: "^8.12.0",
pgvector: "^0.2.0",
"@llamaindex/readers": "^2.0.0",
};
}
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") {
+42
View File
@@ -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;
}
+83
View File
@@ -0,0 +1,83 @@
{
"name": "create-llama",
"version": "0.5.11",
"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",
"format": "prettier --ignore-unknown --cache --check .",
"format:write": "prettier --ignore-unknown --write .",
"lint": "eslint . --ignore-pattern dist --ignore-pattern e2e/cache",
"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": {
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.10.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"@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"
}
}
@@ -16,5 +16,6 @@ export const askQuestions = async (
await askProQuestions(args);
return args as unknown as QuestionResults;
}
return await askSimpleQuestions(args);
const results = await askSimpleQuestions(args);
return results;
};
@@ -1,23 +1,12 @@
import prompts from "prompts";
import {
EXAMPLE_10K_SEC_FILES,
EXAMPLE_FILE,
EXAMPLE_GDPR,
} from "../helpers/datasources";
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 =
| "rag"
| "code_artifact"
| "financial_report_agent"
| "form_filling"
| "extractor"
| "contract_review"
| "data_scientist";
type AppType = "agentic_rag" | "financial_report" | "deep_research";
type SimpleAnswers = {
appType: AppType;
@@ -33,23 +22,25 @@ export const askSimpleQuestions = async (
{
type: "select",
name: "appType",
message: "What app do you want to build?",
message: "What use case do you want to build?",
choices: [
{ title: "Agentic RAG", value: "rag" },
{ title: "Data Scientist", value: "data_scientist" },
{
title: "Financial Report Generator (using Workflows)",
value: "financial_report_agent",
title: "Agentic RAG",
value: "agentic_rag",
description:
"Chatbot that answers questions based on provided documents.",
},
{
title: "Form Filler (using Workflows)",
value: "form_filling",
title: "Financial Report",
value: "financial_report",
description:
"Agent that analyzes data and generates visualizations by using a code interpreter.",
},
{ title: "Code Artifact Agent", value: "code_artifact" },
{ title: "Information Extractor", value: "extractor" },
{
title: "Contract Review (using Workflows)",
value: "contract_review",
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.",
},
],
},
@@ -58,6 +49,7 @@ export const askSimpleQuestions = async (
let language: TemplateFramework = "fastapi";
let llamaCloudKey = args.llamaCloudKey;
let useLlamaCloud = false;
if (appType !== "extractor" && appType !== "contract_review") {
@@ -131,73 +123,36 @@ const convertAnswers = async (
};
const lookup: Record<
AppType,
Pick<
QuestionResults,
"template" | "tools" | "frontend" | "dataSources" | "useCase"
> & {
Pick<QuestionResults, "template" | "tools" | "dataSources" | "useCase"> & {
modelConfig?: ModelConfig;
}
> = {
rag: {
template: "streaming",
tools: getTools(["weather"]),
frontend: true,
agentic_rag: {
template: "llamaindexserver",
dataSources: [EXAMPLE_FILE],
},
data_scientist: {
template: "streaming",
financial_report: {
template: "llamaindexserver",
dataSources: EXAMPLE_10K_SEC_FILES,
tools: getTools(["interpreter", "document_generator"]),
frontend: true,
dataSources: [],
modelConfig: MODEL_GPT4o,
},
code_artifact: {
template: "streaming",
tools: getTools(["artifact"]),
frontend: true,
dataSources: [],
modelConfig: MODEL_GPT4o,
},
financial_report_agent: {
template: "multiagent",
useCase: "financial_report",
tools: getTools(["document_generator", "interpreter"]),
deep_research: {
template: "llamaindexserver",
dataSources: EXAMPLE_10K_SEC_FILES,
frontend: true,
modelConfig: MODEL_GPT4o,
},
form_filling: {
template: "multiagent",
useCase: "form_filling",
tools: getTools(["form_filling"]),
dataSources: EXAMPLE_10K_SEC_FILES,
frontend: true,
modelConfig: MODEL_GPT4o,
},
extractor: {
template: "reflex",
useCase: "extractor",
tools: [],
frontend: false,
dataSources: [EXAMPLE_FILE],
},
contract_review: {
template: "reflex",
useCase: "contract_review",
tools: [],
frontend: false,
dataSources: [EXAMPLE_GDPR],
modelConfig: MODEL_GPT4o,
},
};
const results = lookup[answers.appType];
return {
framework: answers.language,
useCase: answers.appType,
ui: "shadcn",
llamaCloudKey: answers.llamaCloudKey,
useLlamaParse: answers.useLlamaCloud,
llamapack: "",
vectorDb: answers.useLlamaCloud ? "llamacloud" : "none",
observability: "none",
...results,
modelConfig:
results.modelConfig ??
@@ -206,6 +161,6 @@ const convertAnswers = async (
askModels: args.askModels ?? false,
framework: answers.language,
})),
frontend: answers.language === "nextjs" ? false : results.frontend,
frontend: true,
};
};
@@ -19,20 +19,20 @@ 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 dev
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`.
@@ -52,7 +52,7 @@ Open [http://localhost:8000](http://localhost:8000) with your browser to start t
To start the app optimized for **production**, run:
```
poetry run prod
uv run prod
```
## Deployments
@@ -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!
@@ -0,0 +1,3 @@
from .deep_research import create_workflow
__all__ = ["create_workflow"]
@@ -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
@@ -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,
)
@@ -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()
@@ -7,7 +7,7 @@ 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 and `E2B_API_KEY` for the [E2B's code interpreter tool](https://e2b.dev/docs)).
@@ -15,13 +15,13 @@ Then check the parameters that have been pre-configured in the `.env` file in th
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 dev
uv run dev
```
The example provides one streaming API endpoint `/api/chat`.
@@ -40,7 +40,7 @@ Open [http://localhost:8000](http://localhost:8000) with your browser to start t
To start the app optimized for **production**, run:
```
poetry run prod
uv run prod
```
## Deployments
@@ -1,13 +1,5 @@
from typing import Any, Dict, List, Optional
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,
)
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
@@ -22,9 +14,17 @@ from llama_index.core.workflow import (
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(
chat_history: Optional[List[ChatMessage]] = None,
params: Optional[Dict[str, Any]] = None,
**kwargs,
) -> Workflow:
@@ -45,7 +45,6 @@ def create_workflow(
query_engine_tool=query_engine_tool,
code_interpreter_tool=code_interpreter_tool,
document_generator_tool=document_generator_tool,
chat_history=chat_history,
)
@@ -91,6 +90,7 @@ class FinancialReportWorkflow(Workflow):
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,
@@ -99,12 +99,10 @@ class FinancialReportWorkflow(Workflow):
document_generator_tool: FunctionTool,
llm: Optional[FunctionCallingLLM] = None,
timeout: int = 360,
chat_history: Optional[List[ChatMessage]] = None,
system_prompt: Optional[str] = None,
):
super().__init__(timeout=timeout)
self.system_prompt = system_prompt or self._default_system_prompt
self.chat_history = chat_history or []
self.query_engine_tool = query_engine_tool
self.code_interpreter_tool = code_interpreter_tool
self.document_generator_tool = document_generator_tool
@@ -122,13 +120,19 @@ class FinancialReportWorkflow(Workflow):
]
self.llm: FunctionCallingLLM = llm or Settings.llm
assert isinstance(self.llm, FunctionCallingLLM)
self.memory = ChatMemoryBuffer.from_defaults(
llm=self.llm, chat_history=self.chat_history
)
self.memory = ChatMemoryBuffer.from_defaults(llm=self.llm)
@step()
async def prepare_chat_history(self, ctx: Context, ev: StartEvent) -> InputEvent:
ctx.data["input"] = ev.input
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(
@@ -136,9 +140,6 @@ class FinancialReportWorkflow(Workflow):
)
self.memory.put(system_msg)
# Add user input to memory
self.memory.put(ChatMessage(role=MessageRole.USER, content=ev.input))
return InputEvent(input=self.memory.get())
@step()
@@ -160,8 +161,10 @@ class FinancialReportWorkflow(Workflow):
chat_history,
)
if not response.has_tool_calls():
# If no tool call, return the response generator
return StopEvent(result=response.generator)
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():
@@ -7,7 +7,7 @@ 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.
@@ -16,7 +16,7 @@ Make sure you have the `OPENAI_API_KEY` set.
Second, run the development server:
```shell
poetry run dev
uv run dev
```
## Use Case: Filling Financial CSV Template
@@ -46,7 +46,7 @@ Open [http://localhost:8000](http://localhost:8000) with your browser to start t
To start the app optimized for **production**, run:
```
poetry run prod
uv run prod
```
## Deployments
@@ -25,7 +25,6 @@ from app.workflows.tools import (
def create_workflow(
chat_history: Optional[List[ChatMessage]] = None,
params: Optional[Dict[str, Any]] = None,
**kwargs,
) -> Workflow:
@@ -45,7 +44,6 @@ def create_workflow(
query_engine_tool=query_engine_tool,
extractor_tool=extractor_tool, # type: ignore
filling_tool=filling_tool, # type: ignore
chat_history=chat_history,
)
return workflow
@@ -88,6 +86,7 @@ class FormFillingWorkflow(Workflow):
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,
@@ -96,12 +95,10 @@ class FormFillingWorkflow(Workflow):
filling_tool: FunctionTool,
llm: Optional[FunctionCallingLLM] = None,
timeout: int = 360,
chat_history: Optional[List[ChatMessage]] = None,
system_prompt: Optional[str] = None,
):
super().__init__(timeout=timeout)
self.system_prompt = system_prompt or self._default_system_prompt
self.chat_history = chat_history or []
self.query_engine_tool = query_engine_tool
self.extractor_tool = extractor_tool
self.filling_tool = filling_tool
@@ -113,13 +110,18 @@ class FormFillingWorkflow(Workflow):
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, chat_history=self.chat_history
)
self.memory = ChatMemoryBuffer.from_defaults(llm=self.llm)
@step()
async def start(self, ctx: Context, ev: StartEvent) -> InputEvent:
ctx.data["input"] = ev.input
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(
@@ -127,12 +129,7 @@ class FormFillingWorkflow(Workflow):
)
self.memory.put(system_msg)
user_input = ev.input
user_msg = ChatMessage(role=MessageRole.USER, content=user_input)
self.memory.put(user_msg)
chat_history = self.memory.get()
return InputEvent(input=chat_history)
return InputEvent(input=self.memory.get())
@step()
async def handle_llm_input( # type: ignore
@@ -150,7 +147,10 @@ class FormFillingWorkflow(Workflow):
chat_history,
)
if not response.has_tool_calls():
return StopEvent(result=response.generator)
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():

Some files were not shown because too many files have changed in this diff Show More