mirror of
https://github.com/run-llama/create-llama.git
synced 2026-07-15 13:15:42 -04:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fa675c839 | |||
| ebf0b497a4 | |||
| f6dcd86252 | |||
| db178fc1d5 | |||
| 52a81e28a3 | |||
| 8e2cb45c00 | |||
| eb572508e3 | |||
| 06513ac9f3 | |||
| 2fe0c0afd2 | |||
| 81c37f2b60 | |||
| a13dd8a901 | |||
| 90b3e86249 | |||
| e729629b70 | |||
| 792cc04b18 | |||
| c467474ab8 | |||
| 635126e250 | |||
| eaa3b2ea58 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
fix: add trycatch for generating error
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-llama": patch
|
||||
---
|
||||
|
||||
bump: chat-ui and tailwind v4
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": [
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"max-params": [
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"prefer-const": "error",
|
||||
},
|
||||
}
|
||||
+24
-29
@@ -1,15 +1,12 @@
|
||||
name: E2E Tests for create-llama package
|
||||
name: E2E Tests
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- "python/llama-index-server/**"
|
||||
- ".github/workflows/*llama_index_server.yml"
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- "python/llama-index-server/**"
|
||||
- ".github/workflows/*llama_index_server.yml"
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.6.1"
|
||||
|
||||
jobs:
|
||||
e2e-python:
|
||||
@@ -35,10 +32,10 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-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
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
@@ -53,15 +50,15 @@ jobs:
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
working-directory: packages/create-llama
|
||||
working-directory: .
|
||||
|
||||
- name: Build create-llama
|
||||
run: pnpm run build
|
||||
working-directory: packages/create-llama
|
||||
working-directory: .
|
||||
|
||||
- name: Install
|
||||
run: pnpm run pack-install
|
||||
working-directory: packages/create-llama
|
||||
working-directory: .
|
||||
|
||||
- name: Run Playwright tests for Python
|
||||
run: pnpm run e2e:python
|
||||
@@ -70,15 +67,13 @@ jobs:
|
||||
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
|
||||
FRAMEWORK: ${{ matrix.frameworks }}
|
||||
DATASOURCE: ${{ matrix.datasources }}
|
||||
PYTHONIOENCODING: utf-8
|
||||
PYTHONLEGACYWINDOWSSTDIO: utf-8
|
||||
working-directory: packages/create-llama
|
||||
working-directory: .
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-python-${{ matrix.os }}-${{ matrix.frameworks }}-${{ matrix.datasources }}
|
||||
path: packages/create-llama/playwright-report/
|
||||
path: ./playwright-report/
|
||||
overwrite: true
|
||||
retention-days: 30
|
||||
|
||||
@@ -88,10 +83,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
node-version: [18, 20]
|
||||
python-version: ["3.11"]
|
||||
os: [macos-latest, windows-latest, ubuntu-22.04]
|
||||
frameworks: ["nextjs"]
|
||||
frameworks: ["nextjs", "express"]
|
||||
datasources: ["--no-files", "--example-file", "--llamacloud"]
|
||||
defaults:
|
||||
run:
|
||||
@@ -105,10 +100,10 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-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
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
@@ -123,15 +118,15 @@ jobs:
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
working-directory: packages/create-llama
|
||||
working-directory: .
|
||||
|
||||
- name: Build create-llama
|
||||
run: pnpm run build
|
||||
working-directory: packages/create-llama
|
||||
working-directory: .
|
||||
|
||||
- name: Install
|
||||
run: pnpm run pack-install
|
||||
working-directory: packages/create-llama
|
||||
working-directory: .
|
||||
|
||||
- name: Run Playwright tests for TypeScript
|
||||
run: pnpm run e2e:typescript
|
||||
@@ -140,12 +135,12 @@ jobs:
|
||||
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
|
||||
FRAMEWORK: ${{ matrix.frameworks }}
|
||||
DATASOURCE: ${{ matrix.datasources }}
|
||||
working-directory: packages/create-llama
|
||||
working-directory: .
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-typescript-${{ matrix.os }}-${{ matrix.frameworks }}-${{ matrix.datasources }}-node${{ matrix.node-version }}
|
||||
path: packages/create-llama/playwright-report/
|
||||
path: ./playwright-report/
|
||||
overwrite: true
|
||||
retention-days: 30
|
||||
|
||||
@@ -35,10 +35,8 @@ 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"
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "python/llama-index-server/**"
|
||||
- "llama-index-server/**"
|
||||
- ".github/workflows/release_llama_index_server.yml"
|
||||
pull_request:
|
||||
types:
|
||||
@@ -19,11 +19,8 @@ jobs:
|
||||
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') &&
|
||||
!contains(github.event.head_commit.message, 'Release: llama-index-server v')
|
||||
working-directory: ./llama-index-server
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
@@ -31,19 +28,17 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- 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
|
||||
shell: bash
|
||||
run: uv sync --all-extras --dev
|
||||
run: poetry install
|
||||
|
||||
- name: Setup Git
|
||||
run: |
|
||||
@@ -51,17 +46,15 @@ jobs:
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
- name: Bump patch version
|
||||
shell: bash
|
||||
run: |
|
||||
uvx --from=toml-cli toml set --toml-path=pyproject.toml project.version $(uvx --from=toml-cli toml get --toml-path=pyproject.toml project.version | awk -F. '{$NF = $NF + 1;}1' OFS=.)
|
||||
poetry version patch
|
||||
git add pyproject.toml
|
||||
git commit -m "chore(release): bump llama-index-server version to $(uvx --from=toml-cli toml get --toml-path=pyproject.toml project.version)"
|
||||
git commit -m "chore(release): bump version to $(poetry version -s)"
|
||||
|
||||
- name: Get current version
|
||||
id: get_version
|
||||
shell: bash
|
||||
run: |
|
||||
version=$(uvx --from=toml-cli toml get --toml-path=pyproject.toml project.version)
|
||||
version=$(poetry version -s)
|
||||
echo "current_version=${version}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create Release PR
|
||||
@@ -85,7 +78,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./python/llama-index-server
|
||||
working-directory: ./llama-index-server
|
||||
if: |
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
@@ -96,34 +89,25 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: uv sync --all-extras
|
||||
|
||||
- name: Get current version
|
||||
id: get_version
|
||||
shell: bash
|
||||
- name: Install Poetry
|
||||
run: |
|
||||
version=$(uvx --from=toml-cli toml get --toml-path=pyproject.toml project.version)
|
||||
echo "current_version=${version}" >> "$GITHUB_OUTPUT"
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
- name: Install dependencies
|
||||
run: poetry install
|
||||
|
||||
- name: Build package
|
||||
shell: bash
|
||||
run: uv build --no-sources
|
||||
run: poetry build
|
||||
|
||||
- name: Publish to PyPI
|
||||
shell: bash
|
||||
run: uv publish --token ${{ secrets.PYPI_TOKEN }}
|
||||
env:
|
||||
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.LLAMA_INDEX_PYPI_TOKEN }}
|
||||
run: poetry publish --build
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.8.3"
|
||||
PYTHON_VERSION: "3.9"
|
||||
|
||||
jobs:
|
||||
@@ -12,7 +13,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python/llama-index-server
|
||||
working-directory: llama-index-server
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
@@ -20,80 +21,91 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Install Poetry
|
||||
run: pipx install poetry==${{ env.POETRY_VERSION }}
|
||||
|
||||
- name: Set up Python ${{ matrix.python-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: uv sync --all-extras --dev
|
||||
run: poetry install --with dev
|
||||
|
||||
- name: Run unit tests
|
||||
shell: bash
|
||||
run: uv run pytest tests
|
||||
run: |
|
||||
poetry run pytest tests
|
||||
|
||||
type-check:
|
||||
name: Type Check
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python/llama-index-server
|
||||
working-directory: llama-index-server
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
- 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: uv sync --all-extras --dev
|
||||
run: poetry install --with dev
|
||||
|
||||
- name: Run mypy
|
||||
shell: bash
|
||||
run: uv run mypy llama_index
|
||||
run: poetry run mypy llama_index
|
||||
|
||||
build:
|
||||
needs: [unit-test, type-check]
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python/llama-index-server
|
||||
working-directory: llama-index-server
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- 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: Install build package
|
||||
- name: Clear python cache
|
||||
shell: bash
|
||||
run: uv sync --all-extras
|
||||
|
||||
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
|
||||
run: uv run python -c "from llama_index.server import LlamaIndexServer"
|
||||
|
||||
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/
|
||||
path: llama-index-server/dist/
|
||||
|
||||
+26
@@ -6,6 +6,10 @@ node_modules
|
||||
.pnpm-store
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
.coverage
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
@@ -31,9 +35,31 @@ yarn-error.log*
|
||||
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
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
!.vscode/settings.json
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
pnpm format
|
||||
pnpm lint
|
||||
uvx ruff check .
|
||||
uvx ruff format . --check
|
||||
uvx ruff format --check templates/
|
||||
|
||||
+3
-12
@@ -1,15 +1,6 @@
|
||||
node_modules/
|
||||
apps/docs/i18n
|
||||
apps/docs/docs/api
|
||||
pnpm-lock.yaml
|
||||
lib/
|
||||
dist/
|
||||
cache/
|
||||
build/
|
||||
.next/
|
||||
out/
|
||||
packages/server/server/
|
||||
|
||||
# Python
|
||||
python/
|
||||
**/*.mypy_cache/**
|
||||
**/*.venv/**
|
||||
**/*.ruff_cache/**
|
||||
.docusaurus/
|
||||
|
||||
@@ -1,90 +1,5 @@
|
||||
# create-llama
|
||||
|
||||
## 0.5.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 241d82a: Add artifacts use case (python)
|
||||
|
||||
## 0.5.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3960618: chore: create-llama monorepo
|
||||
- 8fe5fc2: chore: add llamaindex server package
|
||||
|
||||
## 0.5.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0a2e12a: Use uv as the default package manager
|
||||
|
||||
## 0.5.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4bc53ac: Bump new chat ui and update deep research component
|
||||
- 4bc53ac: Support generate UI for deep research use case (Typescript)
|
||||
|
||||
## 0.5.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 765181a: chore: test typescript e2e with node 20 and 22
|
||||
|
||||
## 0.5.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5988657: chore: bump llmaindex
|
||||
|
||||
## 0.5.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d363ced: Bump llamaindex server packages
|
||||
|
||||
## 0.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ee85320: The default custom deep research component does not work.
|
||||
|
||||
## 0.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7c3b279: Support code generation of event components using an LLM (Python)
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 76ec360: Update templates to use new chat ui config
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c9f8f8d: Use custom component for deep research use case
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 08b3e07: Simplify the local index code.
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 54c9e2f: Simplified generated code using LlamaIndexServer
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0e4ecfa: fix: add trycatch for generating error
|
||||
- ee69ce7: bump: chat-ui and tailwind v4
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Minor Changes
|
||||
@@ -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
|
||||
uv run generate
|
||||
poetry run generate
|
||||
```
|
||||
|
||||
## Customizing the AI models
|
||||
@@ -130,11 +130,4 @@ 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)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import path from "path";
|
||||
import { green, yellow } from "picocolors";
|
||||
import { tryGitInit } from "./helpers/git";
|
||||
@@ -89,7 +90,7 @@ export async function createApp({
|
||||
// Install backend
|
||||
await installTemplate({ ...args, backend: true });
|
||||
|
||||
if (frontend && framework === "fastapi" && template !== "llamaindexserver") {
|
||||
if (frontend && framework === "fastapi") {
|
||||
// install frontend
|
||||
const frontendRoot = path.join(root, ".frontend");
|
||||
await makeDir(frontendRoot);
|
||||
@@ -109,7 +110,7 @@ export async function createApp({
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (toolsRequireConfig(tools) && template !== "llamaindexserver") {
|
||||
if (toolsRequireConfig(tools)) {
|
||||
const configFile =
|
||||
framework === "fastapi" ? "config/tools.yaml" : "config/tools.json";
|
||||
console.log(
|
||||
+15
-30
@@ -195,47 +195,32 @@ async function createAndCheckLlamaProject({
|
||||
const pyprojectPath = path.join(projectPath, "pyproject.toml");
|
||||
expect(fs.existsSync(pyprojectPath)).toBeTruthy();
|
||||
|
||||
// Modify environment for the command
|
||||
const commandEnv = {
|
||||
const env = {
|
||||
...process.env,
|
||||
POETRY_VIRTUALENVS_IN_PROJECT: "true",
|
||||
};
|
||||
|
||||
console.log("Running uv venv...");
|
||||
// Run poetry install
|
||||
try {
|
||||
const { stdout: venvStdout, stderr: venvStderr } = await execAsync(
|
||||
"uv venv",
|
||||
{ cwd: projectPath, env: commandEnv },
|
||||
const { stdout: installStdout, stderr: installStderr } = await execAsync(
|
||||
"poetry install",
|
||||
{ cwd: projectPath, env },
|
||||
);
|
||||
console.log("uv venv stdout:", venvStdout);
|
||||
console.error("uv venv stderr:", venvStderr);
|
||||
console.log("poetry install stdout:", installStdout);
|
||||
console.error("poetry install stderr:", installStderr);
|
||||
} catch (error) {
|
||||
console.error("Error running uv venv:", error);
|
||||
throw error; // Re-throw error to fail the test
|
||||
console.error("Error running poetry install:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
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 ....");
|
||||
// Run poetry run mypy
|
||||
try {
|
||||
const { stdout: mypyStdout, stderr: mypyStderr } = await execAsync(
|
||||
"uv run mypy .",
|
||||
{ cwd: projectPath, env: commandEnv },
|
||||
"poetry run mypy .",
|
||||
{ cwd: projectPath, env },
|
||||
);
|
||||
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
|
||||
console.log("poetry run mypy stdout:", mypyStdout);
|
||||
console.error("poetry run mypy stderr:", mypyStderr);
|
||||
} catch (error) {
|
||||
console.error("Error running mypy:", error);
|
||||
throw error;
|
||||
+10
-19
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { ChildProcess } from "child_process";
|
||||
import fs from "fs";
|
||||
@@ -15,17 +16,15 @@ const templateFramework: TemplateFramework = process.env.FRAMEWORK
|
||||
const dataSource: string = "--example-file";
|
||||
const templateUI: TemplateUI = "shadcn";
|
||||
const templatePostInstallAction: TemplatePostInstallAction = "runApp";
|
||||
const appType: AppType = "--frontend";
|
||||
const appType: AppType = templateFramework === "fastapi" ? "--frontend" : "";
|
||||
const userMessage = "Write a blog post about physical standards for letters";
|
||||
const templateUseCases = ["financial_report", "agentic_rag", "deep_research"];
|
||||
const templateUseCases = ["financial_report", "blog", "form_filling"];
|
||||
|
||||
for (const useCase of templateUseCases) {
|
||||
test.describe(`Test use case ${useCase} ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
|
||||
test.describe(`Test multiagent template ${useCase} ${templateFramework} ${dataSource} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => {
|
||||
test.skip(
|
||||
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.",
|
||||
process.platform !== "linux" || process.env.DATASOURCE === "--no-files",
|
||||
"The multiagent template currently only works with files. We also only run on Linux to speed up tests.",
|
||||
);
|
||||
let port: number;
|
||||
let cwd: string;
|
||||
@@ -39,7 +38,7 @@ for (const useCase of templateUseCases) {
|
||||
cwd = await createTestDir();
|
||||
const result = await runCreateLlama({
|
||||
cwd,
|
||||
templateType: "llamaindexserver",
|
||||
templateType: "multiagent",
|
||||
templateFramework,
|
||||
dataSource,
|
||||
vectorDb,
|
||||
@@ -64,9 +63,7 @@ for (const useCase of templateUseCases) {
|
||||
templateFramework === "express",
|
||||
);
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await expect(page.getByText("Built by LlamaIndex")).toBeVisible({
|
||||
timeout: 5 * 60 * 1000,
|
||||
});
|
||||
await expect(page.getByText("Built by LlamaIndex")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Frontend should be able to submit a message and receive the start of a streamed response", async ({
|
||||
@@ -75,9 +72,9 @@ for (const useCase of templateUseCases) {
|
||||
test.skip(
|
||||
templatePostInstallAction !== "runApp" ||
|
||||
useCase === "financial_report" ||
|
||||
useCase === "deep_research" ||
|
||||
useCase === "form_filling" ||
|
||||
templateFramework === "express",
|
||||
"Skip chat tests for financial report and deep research.",
|
||||
"Skip chat tests for financial report and form filling.",
|
||||
);
|
||||
await page.goto(`http://localhost:${port}`);
|
||||
await page.fill("form textarea", userMessage);
|
||||
@@ -89,12 +86,6 @@ 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();
|
||||
});
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { ChildProcess } from "child_process";
|
||||
import fs from "fs";
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { ChildProcess } from "child_process";
|
||||
import fs from "fs";
|
||||
+1
-1
@@ -74,7 +74,7 @@ test.describe("Test resolve TS dependencies", () => {
|
||||
// Install dependencies using pnpm
|
||||
try {
|
||||
const { stderr: installStderr } = await execAsync(
|
||||
"pnpm install --prefer-offline --ignore-workspace",
|
||||
"pnpm install --prefer-offline",
|
||||
{
|
||||
cwd: appDir,
|
||||
},
|
||||
@@ -67,8 +67,8 @@ export async function runCreateLlama({
|
||||
].join("-");
|
||||
|
||||
// Handle different data source types
|
||||
const dataSourceArgs = [];
|
||||
if (dataSource.includes("--web-source")) {
|
||||
let dataSourceArgs = [];
|
||||
if (dataSource.includes("--web-source" || "--db-source")) {
|
||||
const webSource = dataSource.split(" ")[1];
|
||||
dataSourceArgs.push("--web-source", webSource);
|
||||
} else if (dataSource.includes("--db-source")) {
|
||||
@@ -113,12 +113,7 @@ export async function runCreateLlama({
|
||||
if (observability) {
|
||||
commandArgs.push("--observability", observability);
|
||||
}
|
||||
if (
|
||||
(templateType === "multiagent" ||
|
||||
templateType === "reflex" ||
|
||||
templateType === "llamaindexserver") &&
|
||||
useCase
|
||||
) {
|
||||
if ((templateType === "multiagent" || templateType === "reflex") && useCase) {
|
||||
commandArgs.push("--use-case", useCase);
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import eslint from "@eslint/js";
|
||||
import eslintConfigPrettier from "eslint-config-prettier";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
eslintConfigPrettier,
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: "module",
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/create-llama/**"],
|
||||
rules: {
|
||||
"max-params": ["error", 4],
|
||||
"prefer-const": "error",
|
||||
"no-empty": "off",
|
||||
"no-extra-boolean-cast": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-empty-object-type": "off",
|
||||
"@typescript-eslint/no-wrapper-object-types": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/server/**"],
|
||||
rules: {
|
||||
"no-irregular-whitespace": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": [
|
||||
"error",
|
||||
{
|
||||
ignoreRestArgs: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"python/**",
|
||||
"**/*.mypy_cache/**",
|
||||
"**/*.venv/**",
|
||||
"**/*.ruff_cache/**",
|
||||
"**/dist/**",
|
||||
"**/e2e/cache/**",
|
||||
"**/lib/*",
|
||||
"**/.next/**",
|
||||
"**/out/**",
|
||||
"**/node_modules/**",
|
||||
"**/build/**",
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { async as glob } from "fast-glob";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -44,7 +44,6 @@ const renderEnvVar = (envVars: EnvVar[]): string => {
|
||||
const getVectorDBEnvs = (
|
||||
vectorDb?: TemplateVectorDB,
|
||||
framework?: TemplateFramework,
|
||||
template?: TemplateType,
|
||||
): EnvVar[] => {
|
||||
if (!vectorDb || !framework) {
|
||||
return [];
|
||||
@@ -169,7 +168,7 @@ const getVectorDBEnvs = (
|
||||
description:
|
||||
"The organization ID for the LlamaCloud project (uses default organization if not specified)",
|
||||
},
|
||||
...(framework === "nextjs" && template !== "llamaindexserver"
|
||||
...(framework === "nextjs"
|
||||
? // activate index selector per default (not needed for non-NextJS backends as it's handled by createFrontendEnvFile)
|
||||
[
|
||||
{
|
||||
@@ -181,7 +180,7 @@ const getVectorDBEnvs = (
|
||||
]
|
||||
: []),
|
||||
];
|
||||
case "chroma": {
|
||||
case "chroma":
|
||||
const envs = [
|
||||
{
|
||||
name: "CHROMA_COLLECTION",
|
||||
@@ -206,7 +205,6 @@ Otherwise, use CHROMA_HOST and CHROMA_PORT config above`,
|
||||
});
|
||||
}
|
||||
return envs;
|
||||
}
|
||||
case "weaviate":
|
||||
return [
|
||||
{
|
||||
@@ -225,15 +223,13 @@ Otherwise, use CHROMA_HOST and CHROMA_PORT config above`,
|
||||
},
|
||||
];
|
||||
default:
|
||||
return template !== "llamaindexserver"
|
||||
? [
|
||||
{
|
||||
name: "STORAGE_CACHE_DIR",
|
||||
description: "The directory to store the local storage cache.",
|
||||
value: ".cache",
|
||||
},
|
||||
]
|
||||
: [];
|
||||
return [
|
||||
{
|
||||
name: "STORAGE_CACHE_DIR",
|
||||
description: "The directory to store the local storage cache.",
|
||||
value: ".cache",
|
||||
},
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -386,42 +382,38 @@ const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
|
||||
|
||||
const getFrameworkEnvs = (
|
||||
framework: TemplateFramework,
|
||||
template: TemplateType,
|
||||
port?: number,
|
||||
): EnvVar[] => {
|
||||
const sPort = port?.toString() || "8000";
|
||||
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`,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
const result: EnvVar[] = [
|
||||
{
|
||||
name: "FILESERVER_URL_PREFIX",
|
||||
description:
|
||||
"FILESERVER_URL_PREFIX is the URL prefix of the server storing the images generated by the interpreter.",
|
||||
value:
|
||||
framework === "nextjs"
|
||||
? // FIXME: if we are using nextjs, port should be 3000
|
||||
"http://localhost:3000/api/files"
|
||||
: `http://localhost:${sPort}/api/files`,
|
||||
},
|
||||
];
|
||||
if (framework === "fastapi") {
|
||||
result.push(
|
||||
...[
|
||||
{
|
||||
name: "APP_HOST",
|
||||
description: "The address to start the FastAPI app.",
|
||||
description: "The address to start the backend app.",
|
||||
value: "0.0.0.0",
|
||||
},
|
||||
{
|
||||
name: "APP_PORT",
|
||||
description: "The port to start the FastAPI app.",
|
||||
description: "The port to start the backend app.",
|
||||
value: sPort,
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
if (framework === "nextjs" && template !== "llamaindexserver") {
|
||||
if (framework === "nextjs") {
|
||||
result.push({
|
||||
name: "NEXT_PUBLIC_CHAT_API",
|
||||
description:
|
||||
@@ -577,41 +569,25 @@ export const createBackendEnvFile = async (
|
||||
| "port"
|
||||
| "tools"
|
||||
| "observability"
|
||||
| "useLlamaParse"
|
||||
>,
|
||||
) => {
|
||||
// Init env values
|
||||
const envFileName = ".env";
|
||||
const envVars: EnvVar[] = [
|
||||
...(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),
|
||||
...getFrameworkEnvs(opts.framework, opts.template, opts.port),
|
||||
{
|
||||
name: "LLAMA_CLOUD_API_KEY",
|
||||
description: `The Llama Cloud API key.`,
|
||||
value: opts.llamaCloudKey,
|
||||
},
|
||||
// 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),
|
||||
]),
|
||||
...getModelEnvs(opts.modelConfig),
|
||||
...getEngineEnvs(),
|
||||
...getVectorDBEnvs(opts.vectorDb, opts.framework),
|
||||
...getFrameworkEnvs(opts.framework, opts.port),
|
||||
...getToolEnvs(opts.tools),
|
||||
...getTemplateEnvs(opts.template),
|
||||
...getObservabilityEnvs(opts.observability),
|
||||
...getSystemPromptEnv(opts.tools, opts.dataSources, opts.template),
|
||||
];
|
||||
// Render and write env file
|
||||
const content = renderEnvVar(envVars);
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -1,7 +1,7 @@
|
||||
import { callPackageManager } from "./install";
|
||||
|
||||
import path from "path";
|
||||
import picocolors, { cyan } from "picocolors";
|
||||
import { cyan } from "picocolors";
|
||||
|
||||
import fsExtra from "fs-extra";
|
||||
import { writeLoadersConfig } from "./datasources";
|
||||
@@ -9,6 +9,7 @@ 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";
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
TemplateVectorDB,
|
||||
} from "./types";
|
||||
import { installTSTemplate } from "./typescript";
|
||||
import { isHavingUvLockFile, tryUvRun } from "./uv";
|
||||
|
||||
const checkForGenerateScript = (
|
||||
modelConfig: ModelConfig,
|
||||
@@ -41,11 +41,7 @@ const checkForGenerateScript = (
|
||||
missingSettings.push("your LLAMA_CLOUD_API_KEY");
|
||||
}
|
||||
|
||||
if (
|
||||
vectorDb !== undefined &&
|
||||
vectorDb !== "none" &&
|
||||
vectorDb !== "llamacloud"
|
||||
) {
|
||||
if (vectorDb !== "none" && vectorDb !== "llamacloud") {
|
||||
missingSettings.push("your Vector DB environment variables");
|
||||
}
|
||||
|
||||
@@ -64,7 +60,7 @@ async function generateContextData(
|
||||
if (packageManager) {
|
||||
const runGenerate = `${cyan(
|
||||
framework === "fastapi"
|
||||
? "uv run generate"
|
||||
? "poetry run generate"
|
||||
: `${packageManager} run generate`,
|
||||
)}`;
|
||||
|
||||
@@ -78,21 +74,15 @@ async function generateContextData(
|
||||
if (!missingSettings.length) {
|
||||
// If all the required environment variables are set, run the generate script
|
||||
if (framework === "fastapi") {
|
||||
if (isHavingUvLockFile()) {
|
||||
if (isHavingPoetryLockFile()) {
|
||||
console.log(`Running ${runGenerate} to generate the context data.`);
|
||||
const result = tryUvRun("generate");
|
||||
const result = tryPoetryRun("poetry run 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.`);
|
||||
@@ -102,14 +92,14 @@ async function generateContextData(
|
||||
}
|
||||
|
||||
const settingsMessage = `After setting ${missingSettings.join(" and ")}, run ${runGenerate} to generate the context data.`;
|
||||
console.log(picocolors.yellow(`\n${settingsMessage}\n\n`));
|
||||
console.log(`\n${settingsMessage}\n\n`);
|
||||
}
|
||||
}
|
||||
|
||||
const downloadFile = async (url: string, destPath: string) => {
|
||||
const response = await fetch(url);
|
||||
const fileBuffer = await response.arrayBuffer();
|
||||
await fsExtra.writeFile(destPath, new Uint8Array(fileBuffer));
|
||||
await fsExtra.writeFile(destPath, Buffer.from(fileBuffer));
|
||||
};
|
||||
|
||||
const prepareContextData = async (
|
||||
@@ -176,17 +166,6 @@ 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
|
||||
@@ -196,13 +175,26 @@ 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 !== "community" && props.template !== "llamapack") {
|
||||
if (
|
||||
props.template === "streaming" ||
|
||||
props.template === "multiagent" ||
|
||||
props.template === "reflex"
|
||||
) {
|
||||
await createBackendEnvFile(props.root, props);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import spawn from "cross-spawn";
|
||||
import { yellow } from "picocolors";
|
||||
import type { PackageManager } from "./get-pkg-manager";
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { blue, green } from "picocolors";
|
||||
@@ -143,6 +143,6 @@ export const installLlamapackProject = async ({
|
||||
await copyData({ root });
|
||||
await installLlamapackExample({ root, llamapack });
|
||||
if (postInstallAction === "runApp" || postInstallAction === "dependencies") {
|
||||
installPythonDependencies();
|
||||
installPythonDependencies({ noRoot: true });
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function askModelConfig({
|
||||
}: ModelConfigQuestionsParams): Promise<ModelConfig> {
|
||||
let modelProvider: ModelProvider = DEFAULT_MODEL_PROVIDER;
|
||||
if (askModels) {
|
||||
const choices = [
|
||||
let choices = [
|
||||
{ title: "OpenAI", value: "openai" },
|
||||
{ title: "Groq", value: "groq" },
|
||||
{ title: "Ollama", value: "ollama" },
|
||||
@@ -3,16 +3,15 @@ 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";
|
||||
@@ -30,8 +29,6 @@ const getAdditionalDependencies = (
|
||||
dataSources?: TemplateDataSource[],
|
||||
tools?: Tool[],
|
||||
templateType?: TemplateType,
|
||||
observability?: TemplateObservability,
|
||||
// eslint-disable-next-line max-params
|
||||
) => {
|
||||
const dependencies: Dependency[] = [];
|
||||
|
||||
@@ -40,21 +37,21 @@ const getAdditionalDependencies = (
|
||||
case "mongo": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-mongodb",
|
||||
version: ">=0.3.2,<0.4.0",
|
||||
version: "^0.6.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "pg": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-postgres",
|
||||
version: ">=0.3.2,<0.4.0",
|
||||
version: "^0.3.2",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "pinecone": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-pinecone",
|
||||
version: ">=0.4.1,<0.5.0",
|
||||
version: "^0.4.1",
|
||||
constraints: {
|
||||
python: ">=3.11,<3.13",
|
||||
},
|
||||
@@ -64,25 +61,25 @@ const getAdditionalDependencies = (
|
||||
case "milvus": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-milvus",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "^0.3.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "pymilvus",
|
||||
version: ">=2.4.4,<3.0.0",
|
||||
version: "2.4.4",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "astra": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-astra-db",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
version: "^0.4.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "qdrant": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-qdrant",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
version: "^0.4.0",
|
||||
constraints: {
|
||||
python: ">=3.11,<3.13",
|
||||
},
|
||||
@@ -92,21 +89,21 @@ const getAdditionalDependencies = (
|
||||
case "chroma": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-chroma",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
version: "^0.4.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "weaviate": {
|
||||
dependencies.push({
|
||||
name: "llama-index-vector-stores-weaviate",
|
||||
version: ">=1.2.3,<2.0.0",
|
||||
version: "^1.2.3",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "llamacloud":
|
||||
dependencies.push({
|
||||
name: "llama-index-indices-managed-llama-cloud",
|
||||
version: ">=0.6.3,<0.7.0",
|
||||
version: "^0.6.3",
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -119,28 +116,28 @@ const getAdditionalDependencies = (
|
||||
case "file":
|
||||
dependencies.push({
|
||||
name: "docx2txt",
|
||||
version: ">=0.8,<0.9",
|
||||
version: "^0.8",
|
||||
});
|
||||
break;
|
||||
case "web":
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-web",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "^0.3.0",
|
||||
});
|
||||
break;
|
||||
case "db":
|
||||
dependencies.push({
|
||||
name: "llama-index-readers-database",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "^0.3.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "pymysql",
|
||||
version: ">=1.1.0,<2.0.0",
|
||||
version: "^1.1.0",
|
||||
extras: ["rsa"],
|
||||
});
|
||||
dependencies.push({
|
||||
name: "psycopg2-binary",
|
||||
version: ">=2.9.9,<3.0.0",
|
||||
version: "^2.9.9",
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -159,122 +156,155 @@ const getAdditionalDependencies = (
|
||||
case "ollama":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-ollama",
|
||||
version: ">=0.5.0,<0.6.0",
|
||||
version: "0.3.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-ollama",
|
||||
version: ">=0.6.0,<0.7.0",
|
||||
version: "0.3.0",
|
||||
});
|
||||
break;
|
||||
case "openai":
|
||||
if (templateType !== "multiagent") {
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-openai",
|
||||
version: ">=0.3.2,<0.4.0",
|
||||
version: "^0.3.2",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-openai",
|
||||
version: ">=0.3.1,<0.4.0",
|
||||
version: "^0.3.1",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-agent-openai",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
version: "^0.4.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.3.0,<0.4.0",
|
||||
version: "0.2.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-fastembed",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "^0.2.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.6.0,<0.7.0",
|
||||
version: "0.3.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-fastembed",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "^0.2.0",
|
||||
});
|
||||
break;
|
||||
case "gemini":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-gemini",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
version: "0.3.4",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-gemini",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "^0.2.0",
|
||||
});
|
||||
break;
|
||||
case "mistral":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-mistralai",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
version: "0.2.1",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-mistralai",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "0.2.0",
|
||||
});
|
||||
break;
|
||||
case "azure-openai":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-azure-openai",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "0.2.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-azure-openai",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "0.2.4",
|
||||
});
|
||||
break;
|
||||
case "huggingface":
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-huggingface",
|
||||
version: ">=0.5.0,<0.6.0",
|
||||
version: "^0.3.5",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-embeddings-huggingface",
|
||||
version: ">=0.5.0,<0.6.0",
|
||||
version: "^0.3.1",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "optimum",
|
||||
version: ">=1.23.3,<2.0.0",
|
||||
version: "^1.23.3",
|
||||
extras: ["onnxruntime"],
|
||||
});
|
||||
break;
|
||||
case "t-systems":
|
||||
dependencies.push({
|
||||
name: "llama-index-agent-openai",
|
||||
version: ">=0.4.0,<0.5.0",
|
||||
version: "0.3.0",
|
||||
});
|
||||
dependencies.push({
|
||||
name: "llama-index-llms-openai-like",
|
||||
version: ">=0.3.0,<0.4.0",
|
||||
version: "0.2.0",
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (observability && observability !== "none") {
|
||||
if (observability === "traceloop") {
|
||||
dependencies.push({
|
||||
name: "traceloop-sdk",
|
||||
version: ">=0.15.11,<0.16.0",
|
||||
});
|
||||
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 === "llamatrace") {
|
||||
dependencies.push({
|
||||
name: "llama-index-callbacks-arize-phoenix",
|
||||
version: ">=0.3.0,<0.4.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;
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
const copyRouterCode = async (root: string, tools: Tool[]) => {
|
||||
@@ -299,100 +329,19 @@ export const addDependencies = async (
|
||||
// Parse toml file
|
||||
const file = path.join(projectDir, FILENAME);
|
||||
const fileContent = await fs.readFile(file, "utf8");
|
||||
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.`,
|
||||
);
|
||||
}
|
||||
const fileParsed = parse(fileContent);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Modify toml dependencies
|
||||
const tool = fileParsed.tool as any;
|
||||
const existingDependencies = tool.poetry.dependencies;
|
||||
mergePoetryDependencies(dependencies, existingDependencies);
|
||||
|
||||
// Write toml file
|
||||
const newFileContent = stringify(fileParsed);
|
||||
await fs.writeFile(file, newFileContent);
|
||||
|
||||
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
|
||||
}
|
||||
const dependenciesString = dependencies.map((d) => d.name).join(", ");
|
||||
console.log(`\nAdded ${dependenciesString} to ${cyan(FILENAME)}\n`);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`Error while updating dependencies for Poetry project file ${FILENAME}\n`,
|
||||
@@ -401,16 +350,18 @@ export const addDependencies = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const installPythonDependencies = () => {
|
||||
if (isUvAvailable()) {
|
||||
export const installPythonDependencies = (
|
||||
{ noRoot }: { noRoot: boolean } = { noRoot: false },
|
||||
) => {
|
||||
if (isPoetryAvailable()) {
|
||||
console.log(
|
||||
`Installing Python dependencies using uv. This may take a while...`,
|
||||
`Installing python dependencies using poetry. This may take a while...`,
|
||||
);
|
||||
const installSuccessful = tryUvSync();
|
||||
const installSuccessful = tryPoetryInstall(noRoot);
|
||||
if (!installSuccessful) {
|
||||
console.error(
|
||||
red(
|
||||
"Installing dependencies using uv failed. Please check the error log above and ensure uv is installed correctly.",
|
||||
"Installing dependencies using poetry failed. Please check error log above and try running create-llama again.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -418,34 +369,57 @@ export const installPythonDependencies = () => {
|
||||
} else {
|
||||
console.error(
|
||||
red(
|
||||
`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.`,
|
||||
`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.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const installLegacyPythonTemplate = async ({
|
||||
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", "streaming", framework);
|
||||
}
|
||||
await copy("**", root, {
|
||||
parents: true,
|
||||
cwd: templatePath,
|
||||
rename: assetRelocator,
|
||||
});
|
||||
|
||||
const compPath = path.join(templatesDir, "components");
|
||||
const enginePath = path.join(root, "app", "engine");
|
||||
|
||||
@@ -535,7 +509,34 @@ const installLegacyPythonTemplate = 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",
|
||||
@@ -547,133 +548,6 @@ const installLegacyPythonTemplate = async ({
|
||||
cwd: templateObservabilityPath,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const installLlamaIndexServerTemplate = async ({
|
||||
root,
|
||||
useCase,
|
||||
useLlamaParse,
|
||||
}: Pick<InstallTemplateArgs, "root" | "useCase" | "useLlamaParse">) => {
|
||||
if (!useCase) {
|
||||
console.log(
|
||||
red(
|
||||
`There is no use case selected. Please pick a use case to use via --use-case flag.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await copy("*.py", path.join(root, "app"), {
|
||||
parents: true,
|
||||
cwd: path.join(templatesDir, "components", "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,24 +34,14 @@ export function runReflexApp(appPath: string, port: number) {
|
||||
"--frontend-port",
|
||||
port.toString(),
|
||||
];
|
||||
return createProcess("uv", commandArgs, {
|
||||
return createProcess("poetry", commandArgs, {
|
||||
stdio: "inherit",
|
||||
cwd: appPath,
|
||||
});
|
||||
}
|
||||
|
||||
export function runFastAPIApp(
|
||||
appPath: string,
|
||||
port: number,
|
||||
template: TemplateType,
|
||||
) {
|
||||
let commandArgs: string[];
|
||||
if (template === "streaming") {
|
||||
commandArgs = ["run", "dev"];
|
||||
} else {
|
||||
commandArgs = ["run", "fastapi", "dev", "--port", `${port}`];
|
||||
}
|
||||
return createProcess("uv", commandArgs, {
|
||||
export function runFastAPIApp(appPath: string, port: number) {
|
||||
return createProcess("poetry", ["run", "dev"], {
|
||||
stdio: "inherit",
|
||||
cwd: appPath,
|
||||
env: { ...process.env, APP_PORT: `${port}` },
|
||||
@@ -83,7 +73,7 @@ export async function runApp(
|
||||
: framework === "fastapi"
|
||||
? runFastAPIApp
|
||||
: runTSApp;
|
||||
await appRunner(appPath, port || defaultPort, template);
|
||||
await appRunner(appPath, port || defaultPort);
|
||||
} 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,<0.4.0",
|
||||
version: "^0.3.0",
|
||||
},
|
||||
],
|
||||
supportedFrameworks: ["fastapi"],
|
||||
@@ -62,7 +62,7 @@ export const supportedTools: Tool[] = [
|
||||
dependencies: [
|
||||
{
|
||||
name: "duckduckgo-search",
|
||||
version: ">=6.3.5,<7.0.0",
|
||||
version: "^6.3.5",
|
||||
},
|
||||
],
|
||||
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,<0.4.0",
|
||||
version: "^0.3.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,<0.3.0",
|
||||
version: "^0.2.14",
|
||||
},
|
||||
{
|
||||
name: "markdown",
|
||||
version: ">=3.7.0,<4.0.0",
|
||||
version: "^3.7",
|
||||
},
|
||||
],
|
||||
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.1.1,<1.2.0",
|
||||
version: "1.0.3",
|
||||
},
|
||||
],
|
||||
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.1.1,<1.2.0",
|
||||
version: "1.0.3",
|
||||
},
|
||||
],
|
||||
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,<5.0.0",
|
||||
version: "^4.22.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,<3.0.0",
|
||||
version: "^2.2.3",
|
||||
},
|
||||
{
|
||||
name: "tabulate",
|
||||
version: ">=0.9.0,<1.0.0",
|
||||
version: "^0.9.0",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -24,8 +24,7 @@ export type TemplateType =
|
||||
| "community"
|
||||
| "llamapack"
|
||||
| "multiagent"
|
||||
| "reflex"
|
||||
| "llamaindexserver";
|
||||
| "reflex";
|
||||
export type TemplateFramework = "nextjs" | "express" | "fastapi";
|
||||
export type TemplateUI = "html" | "shadcn";
|
||||
export type TemplateVectorDB =
|
||||
@@ -56,9 +55,7 @@ export type TemplateUseCase =
|
||||
| "deep_research"
|
||||
| "form_filling"
|
||||
| "extractor"
|
||||
| "contract_review"
|
||||
| "agentic_rag"
|
||||
| "artifacts";
|
||||
| "contract_review";
|
||||
// Config for both file and folder
|
||||
export type FileSourceConfig =
|
||||
| {
|
||||
@@ -8,104 +8,42 @@ import { templatesDir } from "./dir";
|
||||
import { PackageManager } from "./get-pkg-manager";
|
||||
import { InstallTemplateArgs, ModelProvider, TemplateVectorDB } from "./types";
|
||||
|
||||
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 ({
|
||||
/**
|
||||
* Install a LlamaIndex internal template to a given `root` directory.
|
||||
*/
|
||||
export const installTSTemplate = async ({
|
||||
appName,
|
||||
root,
|
||||
packageManager,
|
||||
isOnline,
|
||||
template,
|
||||
backend,
|
||||
framework,
|
||||
ui,
|
||||
vectorDb,
|
||||
postInstallAction,
|
||||
backend,
|
||||
observability,
|
||||
tools,
|
||||
dataSources,
|
||||
useLlamaParse,
|
||||
useCase,
|
||||
modelConfig,
|
||||
relativeEngineDestPath,
|
||||
}: InstallTemplateArgs & {
|
||||
backend: boolean;
|
||||
relativeEngineDestPath: string;
|
||||
}) => {
|
||||
}: 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,
|
||||
});
|
||||
|
||||
/**
|
||||
* If next.js is used, update its configuration if necessary
|
||||
*/
|
||||
@@ -160,6 +98,10 @@ const installLegacyTSTemplate = 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
|
||||
@@ -294,75 +236,6 @@ const installLegacyTSTemplate = 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,
|
||||
@@ -375,7 +248,6 @@ export const installTSTemplate = async ({
|
||||
vectorDb,
|
||||
backend,
|
||||
modelConfig,
|
||||
template,
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -390,27 +262,27 @@ const providerDependencies: {
|
||||
[key in ModelProvider]?: Record<string, string>;
|
||||
} = {
|
||||
openai: {
|
||||
"@llamaindex/openai": "^0.2.0",
|
||||
"@llamaindex/openai": "^0.1.52",
|
||||
},
|
||||
gemini: {
|
||||
"@llamaindex/google": "^0.2.0",
|
||||
"@llamaindex/google": "^0.0.7",
|
||||
},
|
||||
ollama: {
|
||||
"@llamaindex/ollama": "^0.1.0",
|
||||
"@llamaindex/ollama": "^0.0.40",
|
||||
},
|
||||
mistral: {
|
||||
"@llamaindex/mistral": "^0.2.0",
|
||||
"@llamaindex/mistral": "^0.0.5",
|
||||
},
|
||||
"azure-openai": {
|
||||
"@llamaindex/openai": "^0.2.0",
|
||||
"@llamaindex/openai": "^0.1.52",
|
||||
},
|
||||
groq: {
|
||||
"@llamaindex/groq": "^0.0.61",
|
||||
"@llamaindex/huggingface": "^0.1.0", // groq uses huggingface as default embedding model
|
||||
"@llamaindex/groq": "^0.0.51",
|
||||
"@llamaindex/huggingface": "^0.0.36", // 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
|
||||
"@llamaindex/anthropic": "^0.1.0",
|
||||
"@llamaindex/huggingface": "^0.0.36", // anthropic uses huggingface as default embedding model
|
||||
},
|
||||
};
|
||||
|
||||
@@ -459,7 +331,6 @@ async function updatePackageJson({
|
||||
vectorDb,
|
||||
backend,
|
||||
modelConfig,
|
||||
template,
|
||||
}: Pick<
|
||||
InstallTemplateArgs,
|
||||
| "root"
|
||||
@@ -470,7 +341,6 @@ async function updatePackageJson({
|
||||
| "observability"
|
||||
| "vectorDb"
|
||||
| "modelConfig"
|
||||
| "template"
|
||||
> & {
|
||||
relativeEngineDestPath: string;
|
||||
backend: boolean;
|
||||
@@ -482,7 +352,7 @@ async function updatePackageJson({
|
||||
packageJson.name = appName;
|
||||
packageJson.version = "0.1.0";
|
||||
|
||||
if (relativeEngineDestPath && template !== "llamaindexserver") {
|
||||
if (relativeEngineDestPath) {
|
||||
// TODO: move script to {root}/scripts for all frameworks
|
||||
// add generate script if using context engine
|
||||
packageJson.scripts = {
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import validateProjectName from "validate-npm-package-name";
|
||||
|
||||
export function validateNpmName(name: string): {
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { execSync } from "child_process";
|
||||
import { Command } from "commander";
|
||||
import fs from "fs";
|
||||
@@ -0,0 +1,55 @@
|
||||
# LlamaIndex Server
|
||||
|
||||
LlamaIndexServer is a FastAPI application that allows you to quickly launch your workflow as an API server.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install llama-index-server
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
# main.py
|
||||
from llama_index.core.agent.workflow import AgentWorkflow
|
||||
from llama_index.core.workflow import Workflow
|
||||
from llama_index.core.tools import FunctionTool
|
||||
from llama_index.server import LlamaIndexServer
|
||||
|
||||
|
||||
# Define a factory function that returns a Workflow or AgentWorkflow
|
||||
def create_workflow() -> Workflow:
|
||||
def fetch_weather(city: str) -> str:
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
return AgentWorkflow.from_tools(
|
||||
tools=[
|
||||
FunctionTool.from_defaults(
|
||||
fn=fetch_weather,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# Create an API server the workflow
|
||||
app = LlamaIndexServer(
|
||||
workflow_factory=create_workflow # Supports Workflow or AgentWorkflow
|
||||
)
|
||||
```
|
||||
|
||||
## Running the server
|
||||
|
||||
- In the same directory as `main.py`, run the following command to start the server:
|
||||
|
||||
```bash
|
||||
fastapi dev
|
||||
```
|
||||
|
||||
- Making a request to the server
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/api/chat" -H "Content-Type: application/json" -d '{"message": "What is the weather in Tokyo?"}'
|
||||
```
|
||||
|
||||
- See the API documentation at `http://localhost:8000/docs`
|
||||
@@ -0,0 +1,3 @@
|
||||
from .server import LlamaIndexServer
|
||||
|
||||
__all__ = ["LlamaIndexServer"]
|
||||
-2
@@ -1,5 +1,4 @@
|
||||
from llama_index.server.api.callbacks.base import EventCallback
|
||||
from llama_index.server.api.callbacks.llamacloud import LlamaCloudFileDownload
|
||||
from llama_index.server.api.callbacks.source_nodes import SourceNodesFromToolCall
|
||||
from llama_index.server.api.callbacks.suggest_next_questions import (
|
||||
SuggestNextQuestions,
|
||||
@@ -9,5 +8,4 @@ __all__ = [
|
||||
"EventCallback",
|
||||
"SourceNodesFromToolCall",
|
||||
"SuggestNextQuestions",
|
||||
"LlamaCloudFileDownload",
|
||||
]
|
||||
+7
-76
@@ -1,14 +1,13 @@
|
||||
import logging
|
||||
import os
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from llama_index.core.schema import NodeWithScore
|
||||
from llama_index.core.types import ChatMessage, MessageRole
|
||||
from llama_index.core.workflow import Event
|
||||
from llama_index.server.settings import server_settings
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
|
||||
@@ -23,7 +22,6 @@ class ChatConfig(BaseModel):
|
||||
class ChatAPIMessage(BaseModel):
|
||||
role: MessageRole
|
||||
content: str
|
||||
annotations: Optional[List[Any]] = None
|
||||
|
||||
def to_llamaindex_message(self) -> ChatMessage:
|
||||
return ChatMessage(role=self.role, content=self.content)
|
||||
@@ -31,7 +29,6 @@ class ChatAPIMessage(BaseModel):
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
messages: List[ChatAPIMessage]
|
||||
data: Optional[Any] = None
|
||||
config: Optional[ChatConfig] = ChatConfig()
|
||||
|
||||
@field_validator("messages")
|
||||
@@ -101,11 +98,13 @@ class SourceNodes(BaseModel):
|
||||
|
||||
@classmethod
|
||||
def get_url_from_metadata(
|
||||
cls,
|
||||
metadata: Dict[str, Any],
|
||||
data_dir: Optional[str] = None,
|
||||
cls, metadata: Dict[str, Any], data_dir: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
url_prefix = server_settings.file_server_url_prefix
|
||||
url_prefix = os.getenv("FILESERVER_URL_PREFIX")
|
||||
if not url_prefix:
|
||||
logger.warning(
|
||||
"Warning: FILESERVER_URL_PREFIX not set in environment variables. Can't use file server"
|
||||
)
|
||||
if data_dir is None:
|
||||
data_dir = "data"
|
||||
file_name = metadata.get("file_name")
|
||||
@@ -136,71 +135,3 @@ class SourceNodes(BaseModel):
|
||||
cls, source_nodes: List[NodeWithScore]
|
||||
) -> List["SourceNodes"]:
|
||||
return [cls.from_source_node(node) for node in source_nodes]
|
||||
|
||||
|
||||
class ComponentDefinition(BaseModel):
|
||||
type: str
|
||||
code: str
|
||||
filename: str
|
||||
|
||||
|
||||
class UIEvent(Event):
|
||||
type: str
|
||||
data: BaseModel
|
||||
|
||||
def to_response(self) -> dict:
|
||||
return {
|
||||
"type": self.type,
|
||||
"data": self.data.model_dump(),
|
||||
}
|
||||
|
||||
|
||||
class ArtifactType(str, Enum):
|
||||
CODE = "code"
|
||||
DOCUMENT = "document"
|
||||
|
||||
|
||||
class CodeArtifactData(BaseModel):
|
||||
file_name: str
|
||||
code: str
|
||||
language: str
|
||||
|
||||
|
||||
class DocumentArtifactData(BaseModel):
|
||||
title: str
|
||||
content: str
|
||||
type: Literal["markdown", "html"]
|
||||
|
||||
|
||||
class Artifact(BaseModel):
|
||||
created_at: Optional[int] = None
|
||||
type: ArtifactType
|
||||
data: Union[CodeArtifactData, DocumentArtifactData]
|
||||
|
||||
@classmethod
|
||||
def from_message(cls, message: ChatAPIMessage) -> Optional["Artifact"]:
|
||||
if not message.annotations or not isinstance(message.annotations, list):
|
||||
return None
|
||||
|
||||
for annotation in message.annotations:
|
||||
if isinstance(annotation, dict) and annotation.get("type") == "artifact":
|
||||
try:
|
||||
artifact = cls.model_validate(annotation.get("data"))
|
||||
return artifact
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Failed to parse artifact from annotation: {annotation}. Error: {e}"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class ArtifactEvent(Event):
|
||||
type: str = "artifact"
|
||||
data: Artifact
|
||||
|
||||
def to_response(self) -> dict:
|
||||
return {
|
||||
"type": self.type,
|
||||
"data": self.data.model_dump(),
|
||||
}
|
||||
+18
-52
@@ -1,28 +1,20 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
from typing import AsyncGenerator, Callable, Union
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, HTTPException
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from llama_index.core.agent.workflow.workflow_events import (
|
||||
AgentInput,
|
||||
AgentSetup,
|
||||
AgentStream,
|
||||
)
|
||||
from llama_index.core.agent.workflow.workflow_events import AgentStream
|
||||
from llama_index.core.workflow import StopEvent, Workflow
|
||||
from llama_index.server.api.callbacks import (
|
||||
EventCallback,
|
||||
LlamaCloudFileDownload,
|
||||
SourceNodesFromToolCall,
|
||||
SuggestNextQuestions,
|
||||
)
|
||||
from llama_index.server.api.callbacks.base import EventCallback
|
||||
from llama_index.server.api.callbacks.stream_handler import StreamHandler
|
||||
from llama_index.server.api.models import ChatRequest
|
||||
from llama_index.server.api.utils.vercel_stream import VercelStreamResponse
|
||||
from llama_index.server.services.llamacloud import LlamaCloudFileService
|
||||
|
||||
|
||||
def chat_router(
|
||||
@@ -32,21 +24,13 @@ def chat_router(
|
||||
router = APIRouter(prefix="/chat")
|
||||
|
||||
@router.post("")
|
||||
async def chat(
|
||||
request: ChatRequest,
|
||||
background_tasks: BackgroundTasks,
|
||||
) -> StreamingResponse:
|
||||
async def chat(request: ChatRequest) -> StreamingResponse:
|
||||
try:
|
||||
user_message = request.messages[-1].to_llamaindex_message()
|
||||
chat_history = [
|
||||
message.to_llamaindex_message() for message in request.messages[:-1]
|
||||
]
|
||||
# detect if the workflow factory has chat_request as a parameter
|
||||
factory_sig = inspect.signature(workflow_factory)
|
||||
if "chat_request" in factory_sig.parameters:
|
||||
workflow = workflow_factory(chat_request=request)
|
||||
else:
|
||||
workflow = workflow_factory()
|
||||
workflow = workflow_factory()
|
||||
workflow_handler = workflow.run(
|
||||
user_msg=user_message.content,
|
||||
chat_history=chat_history,
|
||||
@@ -54,7 +38,6 @@ def chat_router(
|
||||
|
||||
callbacks: list[EventCallback] = [
|
||||
SourceNodesFromToolCall(),
|
||||
LlamaCloudFileDownload(background_tasks),
|
||||
]
|
||||
if request.config and request.config.next_question_suggestions:
|
||||
callbacks.append(SuggestNextQuestions(request))
|
||||
@@ -70,28 +53,6 @@ def chat_router(
|
||||
logger.error(e)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
if LlamaCloudFileService.is_configured():
|
||||
|
||||
@router.get("/config/llamacloud")
|
||||
async def chat_llama_cloud_config() -> dict:
|
||||
if not os.getenv("LLAMA_CLOUD_API_KEY"):
|
||||
raise HTTPException(
|
||||
status_code=500, detail="LlamaCloud API KEY is not configured"
|
||||
)
|
||||
projects = LlamaCloudFileService.get_all_projects_with_pipelines()
|
||||
pipeline = os.getenv("LLAMA_CLOUD_INDEX_NAME")
|
||||
project = os.getenv("LLAMA_CLOUD_PROJECT_NAME")
|
||||
pipeline_config = None
|
||||
if pipeline and project:
|
||||
pipeline_config = {
|
||||
"pipeline": pipeline,
|
||||
"project": project,
|
||||
}
|
||||
return {
|
||||
"projects": projects,
|
||||
"pipeline": pipeline_config,
|
||||
}
|
||||
|
||||
return router
|
||||
|
||||
|
||||
@@ -104,9 +65,7 @@ async def _stream_content(
|
||||
event: Union[AgentStream, StopEvent],
|
||||
) -> AsyncGenerator[str, None]:
|
||||
if isinstance(event, AgentStream):
|
||||
# Normally, if the stream is a tool call, the delta is always empty
|
||||
# so it's not a text stream.
|
||||
if len(event.tool_calls) == 0:
|
||||
if event.delta.strip(): # Only yield non-empty deltas
|
||||
yield event.delta
|
||||
elif isinstance(event, StopEvent):
|
||||
if isinstance(event.result, str):
|
||||
@@ -115,11 +74,20 @@ async def _stream_content(
|
||||
async for chunk in event.result:
|
||||
if isinstance(chunk, str):
|
||||
yield chunk
|
||||
elif hasattr(chunk, "delta") and chunk.delta:
|
||||
elif (
|
||||
hasattr(chunk, "delta") and chunk.delta.strip()
|
||||
): # Only yield non-empty deltas
|
||||
yield chunk.delta
|
||||
|
||||
stream_started = False
|
||||
try:
|
||||
async for event in handler.stream_events():
|
||||
if not stream_started:
|
||||
# Start the stream with an empty message
|
||||
stream_started = True
|
||||
yield VercelStreamResponse.convert_text("")
|
||||
|
||||
# Handle different types of events
|
||||
if isinstance(event, (AgentStream, StopEvent)):
|
||||
async for chunk in _text_stream(event):
|
||||
handler.accumulate_text(chunk)
|
||||
@@ -130,14 +98,12 @@ async def _stream_content(
|
||||
event_response = event.to_response()
|
||||
yield VercelStreamResponse.convert_data(event_response)
|
||||
else:
|
||||
# Ignore unnecessary agent workflow events
|
||||
if not isinstance(event, (AgentInput, AgentSetup)):
|
||||
yield VercelStreamResponse.convert_data(event.model_dump())
|
||||
yield VercelStreamResponse.convert_data(event.model_dump())
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.warning("Client cancelled the request!")
|
||||
await handler.cancel_run()
|
||||
except Exception as e:
|
||||
logger.error(f"Error in stream response: {e}", exc_info=True)
|
||||
logger.error(f"Error in stream response: {e}")
|
||||
yield VercelStreamResponse.convert_error(str(e))
|
||||
await handler.cancel_run()
|
||||
+1
-1
@@ -5,7 +5,7 @@ from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
CHAT_UI_VERSION = "0.1.6"
|
||||
CHAT_UI_VERSION = "0.0.2"
|
||||
|
||||
|
||||
def download_chat_ui(
|
||||
@@ -0,0 +1,134 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from llama_index.core.workflow import Workflow
|
||||
from llama_index.server.api.routers.chat import chat_router
|
||||
from llama_index.server.chat_ui import download_chat_ui
|
||||
|
||||
|
||||
class LlamaIndexServer(FastAPI):
|
||||
workflow_factory: Callable[..., Workflow]
|
||||
api_prefix: str = "/api"
|
||||
include_ui: Optional[bool]
|
||||
verbose: bool = False
|
||||
ui_path: str = ".ui"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
workflow_factory: Callable[..., Workflow],
|
||||
logger: Optional[logging.Logger] = None,
|
||||
use_default_routers: Optional[bool] = False,
|
||||
env: Optional[str] = None,
|
||||
include_ui: Optional[bool] = None,
|
||||
verbose: bool = False,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""
|
||||
Initialize the LlamaIndexServer.
|
||||
|
||||
Args:
|
||||
workflow_factory: A factory function that creates a workflow instance for each request.
|
||||
logger: The logger to use.
|
||||
use_default_routers: Whether to use the default routers (chat, mount `data` and `output` directories).
|
||||
env: The environment to run the server in.
|
||||
include_ui: Whether to show an chat UI in the root path.
|
||||
verbose: Whether to show verbose logs.
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.workflow_factory = workflow_factory
|
||||
self.logger = logger or logging.getLogger("uvicorn")
|
||||
self.verbose = verbose
|
||||
self.include_ui = include_ui # Store the explicitly passed value first
|
||||
|
||||
if use_default_routers:
|
||||
self.add_default_routers()
|
||||
|
||||
if str(env).lower() == "dev":
|
||||
self.allow_cors("*")
|
||||
if self.include_ui is None:
|
||||
self.include_ui = True
|
||||
if self.include_ui is None:
|
||||
self.include_ui = False
|
||||
|
||||
if self.include_ui:
|
||||
self.mount_ui()
|
||||
|
||||
# Default routers
|
||||
def add_default_routers(self) -> None:
|
||||
self.add_chat_router()
|
||||
self.mount_data_dir()
|
||||
self.mount_output_dir()
|
||||
|
||||
def add_chat_router(self) -> None:
|
||||
"""
|
||||
Add the chat router.
|
||||
"""
|
||||
self.include_router(
|
||||
chat_router(
|
||||
self.workflow_factory,
|
||||
self.logger,
|
||||
),
|
||||
prefix=self.api_prefix,
|
||||
)
|
||||
|
||||
def mount_ui(self) -> None:
|
||||
"""
|
||||
Mount the UI.
|
||||
"""
|
||||
# Check if the static folder exists
|
||||
if self.include_ui:
|
||||
if not os.path.exists(self.ui_path):
|
||||
self.logger.warning(
|
||||
f"UI files not found, downloading UI to {self.ui_path}"
|
||||
)
|
||||
download_chat_ui(logger=self.logger, target_path=self.ui_path)
|
||||
self._mount_static_files(directory=self.ui_path, path="/", html=True)
|
||||
|
||||
def mount_data_dir(self, data_dir: str = "data") -> None:
|
||||
"""
|
||||
Mount the data directory.
|
||||
"""
|
||||
self._mount_static_files(
|
||||
directory=data_dir, path=f"{self.api_prefix}/files/data", html=True
|
||||
)
|
||||
|
||||
def mount_output_dir(self, output_dir: str = "output") -> None:
|
||||
"""
|
||||
Mount the output directory.
|
||||
"""
|
||||
self._mount_static_files(
|
||||
directory=output_dir, path=f"{self.api_prefix}/files/output", html=True
|
||||
)
|
||||
|
||||
def _mount_static_files(
|
||||
self, directory: str, path: str, html: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Mount static files from a directory if it exists.
|
||||
"""
|
||||
if os.path.exists(directory):
|
||||
self.logger.info(f"Mounting static files '{directory}' at '{path}'")
|
||||
self.mount(
|
||||
path,
|
||||
StaticFiles(directory=directory, check_dir=False, html=html),
|
||||
name=f"{directory}-static",
|
||||
)
|
||||
|
||||
def allow_cors(self, origin: str = "*") -> None:
|
||||
"""
|
||||
Allow CORS for a specific origin.
|
||||
"""
|
||||
self.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[origin],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
+12
-12
@@ -5,7 +5,6 @@ import uuid
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from llama_index.server.settings import server_settings
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -86,10 +85,20 @@ class FileService:
|
||||
|
||||
logger.info(f"Saved file to {file_path}")
|
||||
|
||||
file_url_prefix = os.getenv("FILESERVER_URL_PREFIX")
|
||||
if file_url_prefix is None:
|
||||
logger.warning(
|
||||
"FILESERVER_URL_PREFIX is not set. Some features may not work correctly."
|
||||
)
|
||||
file_url_prefix = "http://localhost:8000/api/files"
|
||||
file_size = os.path.getsize(file_path)
|
||||
file_url = (
|
||||
f"{server_settings.file_server_url_prefix}/{save_dir}/{new_file_name}"
|
||||
|
||||
file_url = os.path.join(
|
||||
file_url_prefix,
|
||||
save_dir,
|
||||
new_file_name,
|
||||
)
|
||||
|
||||
return DocumentFile(
|
||||
id=file_id,
|
||||
name=new_file_name,
|
||||
@@ -100,15 +109,6 @@ class FileService:
|
||||
refs=None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_file_url(cls, file_name: str, save_dir: Optional[str] = None) -> str:
|
||||
"""
|
||||
Get the URL of a file.
|
||||
"""
|
||||
if save_dir is None:
|
||||
save_dir = os.path.join("output", "uploaded")
|
||||
return f"{server_settings.file_server_url_prefix}/{save_dir}/{file_name}"
|
||||
|
||||
|
||||
def _sanitize_file_name(file_name: str) -> str:
|
||||
"""
|
||||
+8
-14
@@ -99,11 +99,6 @@ HTML_TEMPLATE = """
|
||||
|
||||
|
||||
class DocumentGenerator:
|
||||
def __init__(self, file_server_url_prefix: str):
|
||||
if not file_server_url_prefix:
|
||||
raise ValueError("file_server_url_prefix is required")
|
||||
self.file_server_url_prefix = file_server_url_prefix
|
||||
|
||||
@classmethod
|
||||
def _generate_html_content(cls, original_content: str) -> str:
|
||||
"""
|
||||
@@ -160,8 +155,9 @@ class DocumentGenerator:
|
||||
content=html_content,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generate_document(
|
||||
self, original_content: str, document_type: str, file_name: str
|
||||
cls, original_content: str, document_type: str, file_name: str
|
||||
) -> str:
|
||||
"""
|
||||
To generate document as PDF or HTML file.
|
||||
@@ -179,26 +175,24 @@ class DocumentGenerator:
|
||||
f"Invalid document type: {document_type}. Must be 'pdf' or 'html'."
|
||||
)
|
||||
# Always generate html content first
|
||||
html_content = self._generate_html_content(original_content)
|
||||
html_content = cls._generate_html_content(original_content)
|
||||
|
||||
# Based on the type of document, generate the corresponding file
|
||||
if doc_type == DocumentType.PDF:
|
||||
content = self._generate_pdf(html_content)
|
||||
content = cls._generate_pdf(html_content)
|
||||
file_extension = "pdf"
|
||||
elif doc_type == DocumentType.HTML:
|
||||
content = BytesIO(self._generate_html(html_content).encode("utf-8"))
|
||||
content = BytesIO(cls._generate_html(html_content).encode("utf-8"))
|
||||
file_extension = "html"
|
||||
else:
|
||||
raise ValueError(f"Unexpected document type: {document_type}")
|
||||
|
||||
file_name = self._validate_file_name(file_name)
|
||||
file_name = cls._validate_file_name(file_name)
|
||||
file_path = os.path.join(OUTPUT_DIR, f"{file_name}.{file_extension}")
|
||||
|
||||
self._write_to_file(content, file_path)
|
||||
cls._write_to_file(content, file_path)
|
||||
|
||||
return (
|
||||
f"{self.file_server_url_prefix}/{OUTPUT_DIR}/{file_name}.{file_extension}"
|
||||
)
|
||||
return f"{os.getenv('FILESERVER_URL_PREFIX')}/{OUTPUT_DIR}/{file_name}.{file_extension}"
|
||||
|
||||
@staticmethod
|
||||
def _write_to_file(content: BytesIO, file_path: str) -> None:
|
||||
+17
-6
@@ -4,9 +4,10 @@ import os
|
||||
import uuid
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from llama_index.core.tools import FunctionTool
|
||||
from llama_index.server.services.file import DocumentFile, FileService
|
||||
from pydantic import BaseModel
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
|
||||
@@ -33,24 +34,34 @@ class E2BCodeInterpreter:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: str,
|
||||
api_key: Optional[str] = None,
|
||||
filesever_url_prefix: Optional[str] = None,
|
||||
output_dir: Optional[str] = None,
|
||||
uploaded_files_dir: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
api_key: The API key for the E2B Code Interpreter.
|
||||
api_key: The API key for the E2B Code Interpreter. If not provided, it will be read from the environment variable `E2B_API_KEY`.
|
||||
filesever_url_prefix: The prefix for the file server or loaded from env: `FILESERVER_URL_PREFIX`, default is `/api/files`.
|
||||
output_dir: The directory for the output files. Default is `output/tools`.
|
||||
uploaded_files_dir: The directory for the files to be uploaded to the sandbox. Default is `output/uploaded`.
|
||||
"""
|
||||
self._validate_package()
|
||||
if api_key is None:
|
||||
api_key = os.getenv("E2B_API_KEY")
|
||||
if filesever_url_prefix is None:
|
||||
filesever_url_prefix = os.getenv("FILESERVER_URL_PREFIX", "/api/files")
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
"api_key is required to run code interpreter. Get it here: https://e2b.dev/docs/getting-started/api-key"
|
||||
"E2B_API_KEY key is required to run code interpreter. Get it here: https://e2b.dev/docs/getting-started/api-key"
|
||||
)
|
||||
if output_dir is not None:
|
||||
self.output_dir = output_dir
|
||||
if uploaded_files_dir is not None:
|
||||
self.uploaded_files_dir = uploaded_files_dir
|
||||
self.filesever_url_prefix = filesever_url_prefix
|
||||
self.interpreter = None
|
||||
self.api_key = api_key
|
||||
self.output_dir = output_dir or "output/tools"
|
||||
self.uploaded_files_dir = uploaded_files_dir or "output/uploaded"
|
||||
|
||||
@classmethod
|
||||
def _validate_package(cls) -> None:
|
||||
+8
-6
@@ -1,11 +1,11 @@
|
||||
import logging
|
||||
import uuid
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Callable, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from llama_index.core.base.llms.types import ChatMessage, ChatResponse
|
||||
from llama_index.core.base.llms.types import ChatMessage, ChatResponse, MessageRole
|
||||
from llama_index.core.llms.function_calling import FunctionCallingLLM
|
||||
from llama_index.core.tools import (
|
||||
BaseTool,
|
||||
@@ -19,7 +19,6 @@ from llama_index.core.agent.workflow.workflow_events import ToolCall, ToolCallRe
|
||||
|
||||
logger = logging.getLogger("uvicorn")
|
||||
|
||||
|
||||
class ToolCallOutput(BaseModel):
|
||||
tool_call_id: str
|
||||
tool_output: ToolOutput
|
||||
@@ -119,7 +118,11 @@ async def call_tools(
|
||||
)
|
||||
)
|
||||
return [
|
||||
await call_tool(ctx, tools_by_name[tool_calls[0].tool_name], tool_calls[0])
|
||||
await call_tool(
|
||||
ctx,
|
||||
tools_by_name[tool_calls[0].tool_name],
|
||||
tool_calls[0]
|
||||
)
|
||||
]
|
||||
# Multiple tool calls, show progress
|
||||
tool_call_outputs: list[ToolCallOutput] = []
|
||||
@@ -147,7 +150,7 @@ async def call_tools(
|
||||
raw_output={
|
||||
"error": f"Tool {tool_call.tool_name} does not exist",
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
continue
|
||||
@@ -219,7 +222,6 @@ async def call_tool(
|
||||
tool_output=output,
|
||||
)
|
||||
|
||||
|
||||
async def _tool_call_generator(
|
||||
llm: FunctionCallingLLM,
|
||||
tools: list[BaseTool],
|
||||
Generated
+6024
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
[build-system]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = ["poetry-core"]
|
||||
|
||||
[tool.codespell]
|
||||
check-filenames = true
|
||||
check-hidden = true
|
||||
# Feel free to un-skip examples, and experimental, you will just need to
|
||||
# work through many typos (--write-changes and --interactive will help)
|
||||
skip = "*.csv,*.html,*.json,*.jsonl,*.pdf,*.txt,*.ipynb"
|
||||
|
||||
[tool.mypy]
|
||||
disallow_untyped_defs = true
|
||||
# Remove venv skip when integrated with pre-commit
|
||||
exclude = ["_static", "build", "examples", "notebooks", "venv"]
|
||||
ignore_missing_imports = true
|
||||
namespace_packages = true
|
||||
explicit_package_bases = true
|
||||
python_version = "3.10"
|
||||
|
||||
[tool.poetry]
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
description = "llama-index fastapi server"
|
||||
exclude = ["**/BUILD"]
|
||||
license = "MIT"
|
||||
name = "llama-index-server"
|
||||
packages = [{include = "llama_index/"}]
|
||||
readme = "README.md"
|
||||
version = "0.1.0"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9,<4.0"
|
||||
fastapi = {extras = ["standard"], version = "^0.115.11"}
|
||||
cachetools = "^5.5.2"
|
||||
requests = "^2.32.3"
|
||||
llama-index-core = "^0.12.0"
|
||||
llama-index-readers-file = "^0.4.6"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = {extras = ["jupyter"], version = "<=23.9.1,>=23.7.0"}
|
||||
codespell = {extras = ["toml"], version = ">=v2.2.6"}
|
||||
e2b-code-interpreter = "^1.1.1"
|
||||
ipython = "8.10.0"
|
||||
jupyter = "^1.0.0"
|
||||
markdown = "^3.7"
|
||||
mypy = "1.15.0"
|
||||
pre-commit = "3.2.0"
|
||||
pylint = "2.15.10"
|
||||
pytest = "^8.3.5"
|
||||
pytest-asyncio = "^0.25.3"
|
||||
pytest-mock = "3.11.1"
|
||||
ruff = "0.0.292"
|
||||
tree-sitter-languages = "^1.8.0"
|
||||
types-Deprecated = ">=0.1.0"
|
||||
types-PyYAML = "^6.0.12.12"
|
||||
types-protobuf = "^4.24.0.4"
|
||||
types-redis = "4.5.5.0"
|
||||
types-requests = "2.28.11.8" # TODO: unpin when mypy>0.991
|
||||
types-setuptools = "67.1.0.0"
|
||||
xhtml2pdf = "^0.2.17"
|
||||
pytest-cov = "^6.0.0"
|
||||
+47
-68
@@ -1,12 +1,10 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any, AsyncGenerator
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from llama_index.core.agent.workflow.workflow_events import AgentStream
|
||||
from llama_index.core.types import MessageRole
|
||||
from llama_index.core.workflow import StopEvent
|
||||
from llama_index.core.workflow.handler import WorkflowHandler
|
||||
from llama_index.server.api.models import ChatAPIMessage, ChatRequest
|
||||
@@ -15,19 +13,17 @@ from llama_index.server.api.utils.vercel_stream import VercelStreamResponse
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def logger() -> logging.Logger:
|
||||
def logger():
|
||||
return logging.getLogger("test")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def chat_request() -> ChatRequest:
|
||||
return ChatRequest(
|
||||
messages=[ChatAPIMessage(role=MessageRole.USER, content="test message")]
|
||||
)
|
||||
def chat_request():
|
||||
return ChatRequest(messages=[ChatAPIMessage(role="user", content="test message")])
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_workflow_handler() -> AsyncMock:
|
||||
def mock_workflow_handler():
|
||||
handler = AsyncMock(spec=WorkflowHandler)
|
||||
handler.accumulate_text = MagicMock()
|
||||
return handler
|
||||
@@ -36,11 +32,8 @@ def mock_workflow_handler() -> AsyncMock:
|
||||
class TestEventStream:
|
||||
@pytest.mark.asyncio()
|
||||
async def test_stream_content_with_agent_stream(
|
||||
self,
|
||||
mock_workflow_handler: AsyncMock,
|
||||
chat_request: ChatRequest,
|
||||
logger: logging.Logger,
|
||||
) -> None:
|
||||
self, mock_workflow_handler, chat_request, logger
|
||||
):
|
||||
# Setup
|
||||
mock_workflow_handler.stream_events.return_value = (
|
||||
self._mock_agent_stream_events()
|
||||
@@ -55,17 +48,15 @@ class TestEventStream:
|
||||
]
|
||||
|
||||
# Assert
|
||||
assert len(result) == 2
|
||||
assert result[0] == VercelStreamResponse.convert_text("Hello")
|
||||
assert result[1] == VercelStreamResponse.convert_text(" World")
|
||||
assert len(result) == 3 # Empty start + 2 text chunks
|
||||
assert result[0] == VercelStreamResponse.convert_text("")
|
||||
assert result[1] == VercelStreamResponse.convert_text("Hello")
|
||||
assert result[2] == VercelStreamResponse.convert_text(" World")
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_stream_content_with_stop_event_string(
|
||||
self,
|
||||
mock_workflow_handler: AsyncMock,
|
||||
chat_request: ChatRequest,
|
||||
logger: logging.Logger,
|
||||
) -> None:
|
||||
self, mock_workflow_handler, chat_request, logger
|
||||
):
|
||||
# Setup
|
||||
mock_workflow_handler.stream_events.return_value = (
|
||||
self._mock_stop_event_string()
|
||||
@@ -80,16 +71,14 @@ class TestEventStream:
|
||||
]
|
||||
|
||||
# Assert
|
||||
assert len(result) == 1
|
||||
assert result[0] == VercelStreamResponse.convert_text("Final answer")
|
||||
assert len(result) == 2 # Empty start + result string
|
||||
assert result[0] == VercelStreamResponse.convert_text("")
|
||||
assert result[1] == VercelStreamResponse.convert_text("Final answer")
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_stream_content_with_stop_event_delta_objects(
|
||||
self,
|
||||
mock_workflow_handler: AsyncMock,
|
||||
chat_request: ChatRequest,
|
||||
logger: logging.Logger,
|
||||
) -> None:
|
||||
self, mock_workflow_handler, chat_request, logger
|
||||
):
|
||||
# Setup
|
||||
mock_workflow_handler.stream_events.return_value = (
|
||||
self._mock_stop_event_delta_objects()
|
||||
@@ -104,17 +93,15 @@ class TestEventStream:
|
||||
]
|
||||
|
||||
# Assert
|
||||
assert len(result) == 2
|
||||
assert result[0] == VercelStreamResponse.convert_text("Delta 1")
|
||||
assert result[1] == VercelStreamResponse.convert_text("Delta 2")
|
||||
assert len(result) == 3 # Empty start + 2 delta chunks
|
||||
assert result[0] == VercelStreamResponse.convert_text("")
|
||||
assert result[1] == VercelStreamResponse.convert_text("Delta 1")
|
||||
assert result[2] == VercelStreamResponse.convert_text("Delta 2")
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_stream_content_with_event_with_to_response(
|
||||
self,
|
||||
mock_workflow_handler: AsyncMock,
|
||||
chat_request: ChatRequest,
|
||||
logger: logging.Logger,
|
||||
) -> None:
|
||||
self, mock_workflow_handler, chat_request, logger
|
||||
):
|
||||
# Setup
|
||||
mock_workflow_handler.stream_events.return_value = (
|
||||
self._mock_event_with_to_response()
|
||||
@@ -129,16 +116,14 @@ class TestEventStream:
|
||||
]
|
||||
|
||||
# Assert
|
||||
assert len(result) == 1
|
||||
assert result[0] == VercelStreamResponse.convert_data({"event_type": "test"})
|
||||
assert len(result) == 2 # Empty start + event with to_response
|
||||
assert result[0] == VercelStreamResponse.convert_text("")
|
||||
assert result[1] == VercelStreamResponse.convert_data({"event_type": "test"})
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_stream_content_with_event_with_model_dump(
|
||||
self,
|
||||
mock_workflow_handler: AsyncMock,
|
||||
chat_request: ChatRequest,
|
||||
logger: logging.Logger,
|
||||
) -> None:
|
||||
self, mock_workflow_handler, chat_request, logger
|
||||
):
|
||||
# Setup
|
||||
mock_workflow_handler.stream_events.return_value = (
|
||||
self._mock_event_with_model_dump()
|
||||
@@ -153,19 +138,17 @@ class TestEventStream:
|
||||
]
|
||||
|
||||
# Assert
|
||||
assert len(result) == 1
|
||||
assert result[0] == VercelStreamResponse.convert_data(None) # type: ignore
|
||||
assert len(result) == 2 # Empty start + event with model_dump
|
||||
assert result[0] == VercelStreamResponse.convert_text("")
|
||||
assert result[1] == VercelStreamResponse.convert_data(None)
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_stream_content_with_cancelled_error(
|
||||
self,
|
||||
mock_workflow_handler: AsyncMock,
|
||||
chat_request: ChatRequest,
|
||||
logger: logging.Logger,
|
||||
) -> None:
|
||||
self, mock_workflow_handler, chat_request, logger
|
||||
):
|
||||
# Setup
|
||||
mock_workflow_handler.stream_events.side_effect = asyncio.CancelledError()
|
||||
logger.warning = MagicMock() # type: ignore
|
||||
logger.warning = MagicMock()
|
||||
|
||||
# Execute
|
||||
result = [
|
||||
@@ -182,15 +165,12 @@ class TestEventStream:
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_stream_content_with_exception(
|
||||
self,
|
||||
mock_workflow_handler: AsyncMock,
|
||||
chat_request: ChatRequest,
|
||||
logger: logging.Logger,
|
||||
) -> None:
|
||||
self, mock_workflow_handler, chat_request, logger
|
||||
):
|
||||
# Setup
|
||||
error_message = "Test error"
|
||||
mock_workflow_handler.stream_events.side_effect = Exception(error_message)
|
||||
logger.error = MagicMock() # type: ignore
|
||||
logger.error = MagicMock()
|
||||
|
||||
# Execute
|
||||
result = [
|
||||
@@ -206,7 +186,7 @@ class TestEventStream:
|
||||
mock_workflow_handler.cancel_run.assert_called_once()
|
||||
logger.error.assert_called_once()
|
||||
|
||||
async def _mock_agent_stream_events(self) -> AsyncGenerator[AgentStream, Any]:
|
||||
async def _mock_agent_stream_events(self):
|
||||
yield AgentStream(
|
||||
delta="Hello", response="", current_agent_name="", tool_calls=[], raw=""
|
||||
)
|
||||
@@ -214,9 +194,7 @@ class TestEventStream:
|
||||
delta=" World", response="", current_agent_name="", tool_calls=[], raw=""
|
||||
)
|
||||
|
||||
async def _mock_agent_stream_with_empty_deltas(
|
||||
self,
|
||||
) -> AsyncGenerator[AgentStream, Any]:
|
||||
async def _mock_agent_stream_with_empty_deltas(self):
|
||||
yield AgentStream(
|
||||
delta=" ", # Empty delta with spaces - should be filtered
|
||||
response="",
|
||||
@@ -239,30 +217,31 @@ class TestEventStream:
|
||||
raw="",
|
||||
)
|
||||
|
||||
async def _mock_stop_event_string(self) -> AsyncGenerator[StopEvent, Any]:
|
||||
async def _mock_stop_event_string(self):
|
||||
yield StopEvent(result="Final answer")
|
||||
|
||||
async def _mock_stop_event_delta_objects(self) -> AsyncGenerator[StopEvent, Any]:
|
||||
async def generator() -> AsyncGenerator[Any, Any]:
|
||||
async def _mock_stop_event_delta_objects(self):
|
||||
async def generator():
|
||||
# Create proper objects with delta attribute that can be serialized
|
||||
class ObjectWithDelta:
|
||||
def __init__(self, delta_value: str) -> None:
|
||||
def __init__(self, delta_value) -> None:
|
||||
self.delta = delta_value
|
||||
|
||||
yield ObjectWithDelta("Delta 1")
|
||||
yield ObjectWithDelta("Delta 2")
|
||||
yield ObjectWithDelta(" ") # Should be filtered out by strip check
|
||||
|
||||
yield StopEvent(result=generator())
|
||||
|
||||
async def _mock_dict_event(self) -> AsyncGenerator[dict[Any, Any], Any]:
|
||||
async def _mock_dict_event(self):
|
||||
yield {"key": "value"}
|
||||
|
||||
async def _mock_event_with_to_response(self) -> AsyncGenerator[Any, Any]:
|
||||
async def _mock_event_with_to_response(self):
|
||||
event = MagicMock()
|
||||
event.to_response.return_value = {"event_type": "test"}
|
||||
yield event
|
||||
|
||||
async def _mock_event_with_model_dump(self) -> AsyncGenerator[Any, Any]:
|
||||
async def _mock_event_with_model_dump(self):
|
||||
event = MagicMock()
|
||||
event.model_dump.return_value = {"name": "test_event"}
|
||||
# Override to_response to return None - this means convert_data(None) will be called
|
||||
+6
-4
@@ -3,6 +3,7 @@ import uuid
|
||||
from unittest.mock import mock_open, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from llama_index.server.services.file import FileService, _sanitize_file_name
|
||||
|
||||
|
||||
@@ -53,7 +54,7 @@ class TestFileService:
|
||||
assert result.type == "txt"
|
||||
assert result.size == 11
|
||||
assert result.path == expected_path
|
||||
assert result.url.endswith(expected_path.replace(os.path.sep, "/"))
|
||||
assert result.url.endswith(expected_path)
|
||||
assert result.refs is None
|
||||
|
||||
@patch("uuid.uuid4")
|
||||
@@ -143,7 +144,7 @@ class TestFileService:
|
||||
test_uuid = "12345678-1234-5678-1234-567812345678"
|
||||
mock_uuid.return_value = uuid.UUID(test_uuid)
|
||||
mock_getsize.return_value = 11
|
||||
mock_getenv.return_value = "/api/files"
|
||||
mock_getenv.return_value = "https://custom-url.com/files"
|
||||
|
||||
# Execute
|
||||
result = FileService.save_file(
|
||||
@@ -157,8 +158,9 @@ class TestFileService:
|
||||
)
|
||||
mock_file_open.assert_called_once_with(expected_path, "wb")
|
||||
assert result.path == expected_path
|
||||
# URL paths must use forward slashes, even on Windows
|
||||
expected_url = f"/api/files/test_dir/test_{test_uuid}.txt"
|
||||
expected_url = os.path.join(
|
||||
"https://custom-url.com/files", "test_dir", f"test_{test_uuid}.txt"
|
||||
)
|
||||
assert result.url == expected_url
|
||||
|
||||
def test_save_file_no_extension(self):
|
||||
@@ -0,0 +1,106 @@
|
||||
import pytest
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
|
||||
from llama_index.core.agent.workflow import AgentWorkflow
|
||||
from llama_index.core.llms import MockLLM
|
||||
from llama_index.server import LlamaIndexServer
|
||||
|
||||
|
||||
def fetch_weather(city: str) -> str:
|
||||
"""Fetch the weather for a given city."""
|
||||
return f"The weather in {city} is sunny."
|
||||
|
||||
|
||||
def _agent_workflow() -> AgentWorkflow:
|
||||
# Use MockLLM instead of default OpenAI
|
||||
mock_llm = MockLLM()
|
||||
return AgentWorkflow.from_tools_or_functions(
|
||||
tools_or_functions=[fetch_weather],
|
||||
verbose=True,
|
||||
llm=mock_llm,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def server() -> LlamaIndexServer:
|
||||
"""Fixture to create a LlamaIndexServer instance."""
|
||||
return LlamaIndexServer(
|
||||
workflow_factory=_agent_workflow,
|
||||
verbose=True,
|
||||
use_default_routers=True,
|
||||
mount_ui=False,
|
||||
env="dev",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_server_has_chat_route(server: LlamaIndexServer) -> None:
|
||||
"""Test that the server has the chat API route."""
|
||||
chat_route_exists = any(route.path == "/api/chat" for route in server.routes)
|
||||
assert chat_route_exists, "Chat API route not found in server routes"
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_server_swagger_docs(server: LlamaIndexServer) -> None:
|
||||
"""Test that the server serves Swagger UI docs."""
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=server), base_url="http://test"
|
||||
) as ac:
|
||||
response = await ac.get("/docs")
|
||||
assert response.status_code == 200
|
||||
assert "text/html" in response.headers["content-type"]
|
||||
assert "Swagger UI" in response.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_ui_is_downloaded(server: LlamaIndexServer) -> None:
|
||||
"""
|
||||
Test if the UI is downloaded and mounted correctly.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
|
||||
# Clean up any existing static directory first
|
||||
if os.path.exists(".ui"):
|
||||
shutil.rmtree(".ui")
|
||||
|
||||
# Create a new server with UI enabled
|
||||
ui_server = LlamaIndexServer(
|
||||
workflow_factory=_agent_workflow,
|
||||
verbose=True,
|
||||
use_default_routers=True,
|
||||
env="dev",
|
||||
include_ui=True,
|
||||
)
|
||||
|
||||
# Verify that static directory was created with index.html
|
||||
assert os.path.exists("./.ui"), "Static directory was not created"
|
||||
assert os.path.isdir("./.ui"), "Static path is not a directory"
|
||||
assert os.path.exists("./.ui/index.html"), "index.html was not downloaded"
|
||||
|
||||
# Check if the UI is mounted and accessible
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=ui_server), base_url="http://test"
|
||||
) as ac:
|
||||
response = await ac.get("/")
|
||||
assert response.status_code == 200
|
||||
assert "text/html" in response.headers["content-type"]
|
||||
|
||||
# Clean up after test
|
||||
shutil.rmtree("./.ui")
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_ui_is_accessible(server: LlamaIndexServer) -> None:
|
||||
"""
|
||||
Test if the UI is accessible.
|
||||
"""
|
||||
# Manually trigger UI mounting
|
||||
server.mount_ui()
|
||||
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=server), base_url="http://test"
|
||||
) as ac:
|
||||
response = await ac.get("/")
|
||||
assert response.status_code == 200
|
||||
assert "text/html" in response.headers["content-type"]
|
||||
+27
-27
@@ -1,7 +1,9 @@
|
||||
import os
|
||||
from io import BytesIO
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from llama_index.server.tools.document_generator import (
|
||||
OUTPUT_DIR,
|
||||
DocumentGenerator,
|
||||
@@ -9,22 +11,25 @@ from llama_index.server.tools.document_generator import (
|
||||
|
||||
|
||||
class TestDocumentGenerator:
|
||||
@pytest.fixture()
|
||||
def env_setup(self): # type: ignore
|
||||
os.environ["FILESERVER_URL_PREFIX"] = "http://test-server"
|
||||
yield
|
||||
os.environ.pop("FILESERVER_URL_PREFIX", None)
|
||||
|
||||
def test_validate_file_name(self) -> None:
|
||||
# Valid names
|
||||
assert (
|
||||
DocumentGenerator("/api/files")._validate_file_name("valid-name")
|
||||
== "valid-name"
|
||||
)
|
||||
assert DocumentGenerator._validate_file_name("valid-name") == "valid-name"
|
||||
|
||||
# Invalid names
|
||||
with pytest.raises(ValueError):
|
||||
DocumentGenerator("/api/files")._validate_file_name("/invalid/path")
|
||||
DocumentGenerator._validate_file_name("/invalid/path")
|
||||
|
||||
@patch("os.makedirs")
|
||||
@patch("builtins.open")
|
||||
def test_write_to_file(self, mock_open, mock_makedirs): # type: ignore
|
||||
content = BytesIO(b"test")
|
||||
DocumentGenerator("/api/files")._write_to_file(content, "path/file.txt")
|
||||
DocumentGenerator._write_to_file(content, "path/file.txt")
|
||||
|
||||
mock_makedirs.assert_called_once()
|
||||
mock_open.assert_called_once()
|
||||
@@ -37,13 +42,10 @@ class TestDocumentGenerator:
|
||||
mock_markdown.return_value = "<h1>Test</h1>"
|
||||
|
||||
# Test HTML content generation
|
||||
assert (
|
||||
DocumentGenerator("/api/files")._generate_html_content("# Test")
|
||||
== "<h1>Test</h1>"
|
||||
)
|
||||
assert DocumentGenerator._generate_html_content("# Test") == "<h1>Test</h1>"
|
||||
|
||||
# Test full HTML generation
|
||||
html = DocumentGenerator("/api/files")._generate_html("<h1>Test</h1>")
|
||||
html = DocumentGenerator._generate_html("<h1>Test</h1>")
|
||||
assert "<!DOCTYPE html>" in html
|
||||
assert "<h1>Test</h1>" in html
|
||||
|
||||
@@ -51,14 +53,12 @@ class TestDocumentGenerator:
|
||||
def test_pdf_generation(self, mock_pisa): # type: ignore
|
||||
# Success case
|
||||
mock_pisa.return_value = MagicMock(err=None)
|
||||
assert isinstance(
|
||||
DocumentGenerator("/api/files")._generate_pdf("test"), BytesIO
|
||||
)
|
||||
assert isinstance(DocumentGenerator._generate_pdf("test"), BytesIO)
|
||||
|
||||
# Error case
|
||||
mock_pisa.return_value = MagicMock(err="Error")
|
||||
with pytest.raises(ValueError):
|
||||
DocumentGenerator("/api/files")._generate_pdf("test")
|
||||
DocumentGenerator._generate_pdf("test")
|
||||
|
||||
@patch.multiple(
|
||||
DocumentGenerator,
|
||||
@@ -69,21 +69,21 @@ class TestDocumentGenerator:
|
||||
_generate_pdf=MagicMock(return_value=BytesIO(b"pdf")),
|
||||
_write_to_file=MagicMock(),
|
||||
)
|
||||
def test_generate_document(self): # type: ignore
|
||||
def test_generate_document(self, env_setup): # type: ignore
|
||||
# HTML generation
|
||||
url = DocumentGenerator("/api/files").generate_document(
|
||||
"# Test", "html", "test-doc"
|
||||
)
|
||||
assert url == f"/api/files/{OUTPUT_DIR}/test-doc.html"
|
||||
url = DocumentGenerator.generate_document("# Test", "html", "test-doc")
|
||||
assert url == f"http://test-server/{OUTPUT_DIR}/test-doc.html"
|
||||
|
||||
# PDF generation
|
||||
url = DocumentGenerator("/api/files").generate_document(
|
||||
"# Test", "pdf", "test-doc"
|
||||
)
|
||||
assert url == f"/api/files/{OUTPUT_DIR}/test-doc.pdf"
|
||||
url = DocumentGenerator.generate_document("# Test", "pdf", "test-doc")
|
||||
assert url == f"http://test-server/{OUTPUT_DIR}/test-doc.pdf"
|
||||
|
||||
# Invalid type
|
||||
with pytest.raises(ValueError):
|
||||
DocumentGenerator("/api/files").generate_document(
|
||||
"# Test", "invalid", "test-doc"
|
||||
)
|
||||
DocumentGenerator.generate_document("# Test", "invalid", "test-doc")
|
||||
|
||||
def test_to_tool(self): # type: ignore
|
||||
tool = DocumentGenerator().to_tool()
|
||||
# Check the function is correct
|
||||
assert tool.fn == DocumentGenerator.generate_document
|
||||
assert callable(tool.fn)
|
||||
+7
-4
@@ -1,7 +1,9 @@
|
||||
from unittest.mock import MagicMock
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from e2b_code_interpreter.models import Execution, Logs
|
||||
|
||||
from llama_index.server.tools.interpreter import E2BCodeInterpreter
|
||||
|
||||
|
||||
@@ -18,9 +20,10 @@ class TestE2BCodeInterpreter:
|
||||
@pytest.fixture()
|
||||
def code_interpreter(self, sandbox): # type: ignore
|
||||
"""Create E2BCodeInterpreter that uses the mock Sandbox."""
|
||||
interpreter = E2BCodeInterpreter(api_key="dummy_key")
|
||||
interpreter.interpreter = sandbox
|
||||
return interpreter
|
||||
with patch.dict(os.environ, {"E2B_API_KEY": "dummy_key"}):
|
||||
interpreter = E2BCodeInterpreter()
|
||||
interpreter.interpreter = sandbox
|
||||
return interpreter
|
||||
|
||||
def test_interpret_success(self, code_interpreter, sandbox) -> None: # type: ignore
|
||||
"""Test successful code execution."""
|
||||
+63
-32
@@ -1,52 +1,83 @@
|
||||
{
|
||||
"name": "create-llama-monorepo",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Monorepo for create-llama",
|
||||
"name": "create-llama",
|
||||
"version": "0.4.0",
|
||||
"description": "Create LlamaIndex-powered apps with one command",
|
||||
"keywords": [
|
||||
"rag",
|
||||
"llamaindex"
|
||||
"llamaindex",
|
||||
"next.js"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/run-llama/create-llama"
|
||||
"url": "https://github.com/run-llama/create-llama",
|
||||
"directory": "packages/create-llama"
|
||||
},
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
"bin": {
|
||||
"create-llama": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "pnpm -r dev",
|
||||
"build": "pnpm -r build",
|
||||
"e2e": "pnpm -r e2e",
|
||||
"lint": "eslint .",
|
||||
"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",
|
||||
"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"
|
||||
"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.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": {
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"bunchee": "6.4.0",
|
||||
"@playwright/test": "^1.41.1",
|
||||
"@vercel/ncc": "0.38.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"husky": "^9.0.10",
|
||||
"lint-staged": "^15.2.11",
|
||||
"typescript-eslint": "^8.18.0",
|
||||
"globals": "^15.12.0",
|
||||
"eslint": "9.22.0",
|
||||
"@eslint/js": "^9.25.0",
|
||||
"eslint-config-next": "^15.1.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-react": "7.37.2",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"typescript": "^5.7.3",
|
||||
"@types/node": "^22.9.0",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19"
|
||||
"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": {
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# 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,42 +0,0 @@
|
||||
// Migrate poetry to uv
|
||||
import { execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
import { red } from "picocolors";
|
||||
|
||||
export function isUvAvailable(): boolean {
|
||||
try {
|
||||
execSync("uv --version", { stdio: "ignore" });
|
||||
return true;
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function tryUvSync(): boolean {
|
||||
try {
|
||||
console.log("Syncing environment with pyproject.toml...");
|
||||
execSync(`uv sync`, {
|
||||
stdio: "inherit",
|
||||
});
|
||||
return true;
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function tryUvRun(command: string): boolean {
|
||||
try {
|
||||
// Use uv run <command>
|
||||
execSync(`uv run ${command}`, { stdio: "inherit" });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(red(`Failed to run ${command}. Error: ${error}`));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isHavingUvLockFile(): boolean {
|
||||
try {
|
||||
// Check if uv.lock exists in the current directory
|
||||
return fs.existsSync("uv.lock");
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
{
|
||||
"name": "create-llama",
|
||||
"version": "0.5.12",
|
||||
"description": "Create LlamaIndex-powered apps with one command",
|
||||
"keywords": [
|
||||
"rag",
|
||||
"llamaindex",
|
||||
"next.js"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/run-llama/create-llama",
|
||||
"directory": "packages/create-llama"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"create-llama": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"LICENSE.md"
|
||||
],
|
||||
"scripts": {
|
||||
"copy": "cp -r ../../README.md ../../LICENSE.md .",
|
||||
"build": "bash ./scripts/build.sh",
|
||||
"build:ncc": "pnpm run clean && ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
|
||||
"postbuild": "pnpm run copy",
|
||||
"clean": "rimraf --glob ./dist ./templates/**/__pycache__ ./templates/**/node_modules ./templates/**/poetry.lock",
|
||||
"dev": "ncc build ./index.ts -w -o dist/",
|
||||
"e2e": "playwright test",
|
||||
"e2e:python": "playwright test e2e/shared e2e/python",
|
||||
"e2e:typescript": "playwright test e2e/shared e2e/typescript",
|
||||
"pack-install": "bash ./scripts/pack.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/async-retry": "1.4.2",
|
||||
"@types/ci-info": "2.0.0",
|
||||
"@types/cross-spawn": "6.0.0",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/node": "^20.11.7",
|
||||
"@types/prompts": "2.4.2",
|
||||
"@types/tar": "6.1.5",
|
||||
"@types/validate-npm-package-name": "3.0.0",
|
||||
"async-retry": "1.3.1",
|
||||
"async-sema": "3.0.1",
|
||||
"ci-info": "github:watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
|
||||
"commander": "12.1.0",
|
||||
"cross-spawn": "7.0.3",
|
||||
"fast-glob": "3.3.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "10.7.0",
|
||||
"ollama": "^0.5.0",
|
||||
"ora": "^8.0.1",
|
||||
"picocolors": "1.0.0",
|
||||
"prompts": "2.4.2",
|
||||
"smol-toml": "^1.1.4",
|
||||
"tar": "6.1.15",
|
||||
"terminal-link": "^3.0.0",
|
||||
"update-check": "1.5.4",
|
||||
"validate-npm-package-name": "3.0.0",
|
||||
"yaml": "2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.41.1",
|
||||
"@vercel/ncc": "0.38.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"typescript": "^5.3.3",
|
||||
"wait-port": "^1.1.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.0.5",
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
[project]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = [ { name = "Marcus Schiesser", email = "mail@marcusschiesser.de" } ]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11,<4.0"
|
||||
dependencies = [
|
||||
"fastapi>=0.109.1",
|
||||
"python-dotenv>=1.0.0",
|
||||
"pydantic<2.10",
|
||||
"llama-index>=0.12.1",
|
||||
"cachetools>=5.3.3",
|
||||
"reflex>=0.6.2.post1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
generate = "app.engine.generate:generate_datasource"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"mypy>=1.8.0",
|
||||
"pytest>=8.3.5",
|
||||
"pytest-asyncio>=0.25.3",
|
||||
"docx2txt>=0.8",
|
||||
"llama-index-llms-openai>=0.3.2",
|
||||
"llama-index-embeddings-openai>=0.3.1",
|
||||
"llama-index-agent-openai>=0.4.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = [ "hatchling>=1.24" ]
|
||||
build-backend = "hatchling.build"
|
||||
@@ -1,30 +0,0 @@
|
||||
[project]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = [ { name = "Marcus Schiesser", email = "mail@marcusschiesser.de" } ]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11,<4.0"
|
||||
dependencies = [
|
||||
"fastapi>=0.109.1",
|
||||
"uvicorn>=0.23.2",
|
||||
"python-dotenv>=1.0.0",
|
||||
"pydantic<2.10",
|
||||
"llama-index>=0.12.1",
|
||||
"cachetools>=5.3.3",
|
||||
"reflex>=0.6.2.post1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
generate = "app.engine.generate:generate_datasource"
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"mypy>=1.8.0",
|
||||
"pytest>=8.3.5",
|
||||
"pytest-asyncio>=0.25.3",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = [ "hatchling>=1.24" ]
|
||||
build-backend = "hatchling.build"
|
||||
@@ -1,137 +0,0 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Markdown } from "@llamaindex/chat-ui/widgets";
|
||||
import { ListChecks, Loader2, Wand2 } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const STAGE_META = {
|
||||
plan: {
|
||||
icon: ListChecks,
|
||||
badgeText: "Step 1/2: Planning",
|
||||
gradient: "from-blue-100 via-blue-50 to-white",
|
||||
progress: 33,
|
||||
iconBg: "bg-blue-100 text-blue-600",
|
||||
badge: "bg-blue-100 text-blue-700",
|
||||
},
|
||||
generate: {
|
||||
icon: Wand2,
|
||||
badgeText: "Step 2/2: Generating",
|
||||
gradient: "from-violet-100 via-violet-50 to-white",
|
||||
progress: 66,
|
||||
iconBg: "bg-violet-100 text-violet-600",
|
||||
badge: "bg-violet-100 text-violet-700",
|
||||
},
|
||||
};
|
||||
|
||||
function ArtifactWorkflowCard({ event }) {
|
||||
const [visible, setVisible] = useState(event?.state !== "completed");
|
||||
const [fade, setFade] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (event?.state === "completed") {
|
||||
setVisible(false);
|
||||
} else {
|
||||
setVisible(true);
|
||||
setFade(false);
|
||||
}
|
||||
}, [event?.state]);
|
||||
|
||||
if (!event || !visible) return null;
|
||||
|
||||
const { state, requirement } = event;
|
||||
const meta = STAGE_META[state];
|
||||
|
||||
if (!meta) return null;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[180px] w-full items-center justify-center py-2">
|
||||
<Card
|
||||
className={cn(
|
||||
"w-full rounded-xl shadow-md transition-all duration-500",
|
||||
"border-0",
|
||||
fade && "pointer-events-none opacity-0",
|
||||
`bg-gradient-to-br ${meta.gradient}`,
|
||||
)}
|
||||
style={{
|
||||
boxShadow:
|
||||
"0 2px 12px 0 rgba(80, 80, 120, 0.08), 0 1px 3px 0 rgba(80, 80, 120, 0.04)",
|
||||
}}
|
||||
>
|
||||
<CardHeader className="flex flex-row items-center gap-2 px-3 pb-1 pt-2">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center rounded-full p-1",
|
||||
meta.iconBg,
|
||||
)}
|
||||
>
|
||||
<meta.icon className="h-5 w-5" />
|
||||
</div>
|
||||
<CardTitle className="flex items-center gap-2 text-base font-semibold">
|
||||
<Badge className={cn("ml-1", meta.badge, "px-2 py-0.5 text-xs")}>
|
||||
{meta.badgeText}
|
||||
</Badge>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-3 py-1">
|
||||
{state === "plan" && (
|
||||
<div className="flex flex-col items-center gap-2 py-2">
|
||||
<Loader2 className="mb-1 h-6 w-6 animate-spin text-blue-400" />
|
||||
<div className="text-center text-sm font-medium text-blue-900">
|
||||
Analyzing your request...
|
||||
</div>
|
||||
<Skeleton className="mt-1 h-3 w-1/2 rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
{state === "generate" && (
|
||||
<div className="flex flex-col gap-2 py-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-violet-400" />
|
||||
<span className="text-sm font-medium text-violet-900">
|
||||
Working on the requirement:
|
||||
</span>
|
||||
</div>
|
||||
<div className="max-h-24 overflow-auto rounded-lg border border-violet-200 bg-violet-50 px-2 py-1 text-xs">
|
||||
{requirement ? (
|
||||
<Markdown content={requirement} />
|
||||
) : (
|
||||
<span className="italic text-violet-400">
|
||||
No requirements available yet.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
<div className="px-3 pb-2 pt-1">
|
||||
<Progress
|
||||
value={meta.progress}
|
||||
className={cn(
|
||||
"h-1 rounded-full bg-gray-200",
|
||||
state === "plan" && "bg-blue-200",
|
||||
state === "generate" && "bg-violet-200",
|
||||
)}
|
||||
indicatorClassName={cn(
|
||||
"transition-all duration-500",
|
||||
state === "plan" && "bg-blue-500",
|
||||
state === "generate" && "bg-violet-500",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Component({ events }) {
|
||||
const aggregateEvents = () => {
|
||||
if (!events || events.length === 0) return null;
|
||||
return events[events.length - 1];
|
||||
};
|
||||
|
||||
const event = aggregateEvents();
|
||||
|
||||
return <ArtifactWorkflowCard event={event} />;
|
||||
}
|
||||
@@ -1,376 +0,0 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Markdown } from "@llamaindex/chat-ui/widgets";
|
||||
import {
|
||||
AlertCircle,
|
||||
Brain,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
Database,
|
||||
Loader2,
|
||||
MessageSquare,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Component({ events }) {
|
||||
const aggregateEvents = (events) => {
|
||||
if (!events || events.length === 0)
|
||||
return { retrieve: null, analyze: null, answers: [] };
|
||||
|
||||
// Initialize the result structure
|
||||
const result = {
|
||||
retrieve: null,
|
||||
analyze: null,
|
||||
answers: [],
|
||||
};
|
||||
|
||||
// Process each event
|
||||
events.forEach((event) => {
|
||||
const { event: eventType, state, id, question, answer } = event;
|
||||
|
||||
if (eventType === "retrieve") {
|
||||
// Update retrieve status
|
||||
result.retrieve = { state };
|
||||
} else if (eventType === "analyze") {
|
||||
// Update analyze status
|
||||
result.analyze = { state };
|
||||
} else if (eventType === "answer" && id) {
|
||||
// Find existing answer with the same id or create a new one
|
||||
const existingAnswerIndex = result.answers.findIndex(
|
||||
(a) => a.id === id,
|
||||
);
|
||||
|
||||
if (existingAnswerIndex >= 0) {
|
||||
// Update existing answer
|
||||
result.answers[existingAnswerIndex] = {
|
||||
...result.answers[existingAnswerIndex],
|
||||
state,
|
||||
question: question || result.answers[existingAnswerIndex].question,
|
||||
answer: answer || result.answers[existingAnswerIndex].answer,
|
||||
};
|
||||
} else {
|
||||
// Add new answer
|
||||
result.answers.push({
|
||||
id,
|
||||
state,
|
||||
question,
|
||||
answer,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const [aggregatedEvents, setAggregatedEvents] = useState({
|
||||
retrieve: null,
|
||||
analyze: null,
|
||||
answers: [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setAggregatedEvents(aggregateEvents(events));
|
||||
}, [events]);
|
||||
|
||||
const { retrieve, analyze, answers } = aggregatedEvents;
|
||||
|
||||
// Helper function to get status icon
|
||||
const getStatusIcon = (state) => {
|
||||
switch (state) {
|
||||
case "pending":
|
||||
return <Clock className="h-4 w-4 text-gray-400" />;
|
||||
case "inprogress":
|
||||
return <Loader2 className="h-4 w-4 animate-spin text-blue-500" />;
|
||||
case "done":
|
||||
return <CheckCircle className="h-4 w-4 text-green-500" />;
|
||||
case "error":
|
||||
return <AlertCircle className="h-4 w-4 text-red-500" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get status text
|
||||
const getStatusText = (state) => {
|
||||
switch (state) {
|
||||
case "pending":
|
||||
return "Pending";
|
||||
case "inprogress":
|
||||
return "In Progress";
|
||||
case "done":
|
||||
return "Complete";
|
||||
case "error":
|
||||
return "Error";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get status color class
|
||||
const getStatusColorClass = (state) => {
|
||||
switch (state) {
|
||||
case "pending":
|
||||
return "bg-gray-200";
|
||||
case "inprogress":
|
||||
return "bg-blue-500";
|
||||
case "done":
|
||||
return "bg-green-500";
|
||||
case "error":
|
||||
return "bg-red-500";
|
||||
default:
|
||||
return "bg-gray-200";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-4xl space-y-6 p-4">
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">DeepResearch Workflow</h1>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge
|
||||
variant={retrieve?.state === "done" ? "default" : "outline"}
|
||||
className={cn(
|
||||
"transition-all",
|
||||
retrieve?.state === "done" ? "bg-green-500" : "",
|
||||
)}
|
||||
>
|
||||
Retrieve
|
||||
</Badge>
|
||||
<Separator className="h-4 w-px bg-gray-300" orientation="vertical" />
|
||||
<Badge
|
||||
variant={analyze?.state === "done" ? "default" : "outline"}
|
||||
className={cn(
|
||||
"transition-all",
|
||||
analyze?.state === "done" ? "bg-green-500" : "",
|
||||
)}
|
||||
>
|
||||
Analyze
|
||||
</Badge>
|
||||
<Separator className="h-4 w-px bg-gray-300" orientation="vertical" />
|
||||
<Badge
|
||||
variant={
|
||||
answers.length > 0 && answers.every((a) => a.state === "done")
|
||||
? "default"
|
||||
: "outline"
|
||||
}
|
||||
className={cn(
|
||||
"transition-all",
|
||||
answers.length > 0 && answers.every((a) => a.state === "done")
|
||||
? "bg-green-500"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
Answer
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Retrieve Panel */}
|
||||
<Card
|
||||
className={cn(
|
||||
"border-2 transition-all duration-300",
|
||||
retrieve?.state === "inprogress"
|
||||
? "border-blue-500 shadow-lg shadow-blue-100"
|
||||
: retrieve?.state === "done"
|
||||
? "border-green-500"
|
||||
: retrieve?.state === "error"
|
||||
? "border-red-500"
|
||||
: "border-gray-200",
|
||||
)}
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Database className="h-5 w-5 text-gray-700" />
|
||||
<CardTitle>Retrieve Information</CardTitle>
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"flex items-center space-x-1",
|
||||
retrieve?.state === "inprogress"
|
||||
? "text-blue-500"
|
||||
: retrieve?.state === "done"
|
||||
? "text-green-500"
|
||||
: retrieve?.state === "error"
|
||||
? "text-red-500"
|
||||
: "text-gray-500",
|
||||
)}
|
||||
>
|
||||
{getStatusIcon(retrieve?.state)}
|
||||
<span>{getStatusText(retrieve?.state)}</span>
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Retrieving relevant information from the knowledge base
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
{/* Analyze Panel */}
|
||||
{retrieve?.state === "done" && (
|
||||
<Card
|
||||
className={cn(
|
||||
"border-2 transition-all duration-300",
|
||||
analyze?.state === "inprogress"
|
||||
? "border-blue-500 shadow-lg shadow-blue-100"
|
||||
: analyze?.state === "done"
|
||||
? "border-green-500"
|
||||
: analyze?.state === "error"
|
||||
? "border-red-500"
|
||||
: "border-gray-200",
|
||||
)}
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Brain className="h-5 w-5 text-gray-700" />
|
||||
<CardTitle>Analyze Information</CardTitle>
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"flex items-center space-x-1",
|
||||
analyze?.state === "inprogress"
|
||||
? "text-blue-500"
|
||||
: analyze?.state === "done"
|
||||
? "text-green-500"
|
||||
: analyze?.state === "error"
|
||||
? "text-red-500"
|
||||
: "text-gray-500",
|
||||
)}
|
||||
>
|
||||
{getStatusIcon(analyze?.state)}
|
||||
<span>{getStatusText(analyze?.state)}</span>
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Analyzing retrieved information and generating questions
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Answer Panel */}
|
||||
{analyze?.state === "done" && answers.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center space-x-2">
|
||||
<MessageSquare className="h-5 w-5 text-gray-700" />
|
||||
<CardTitle>Answers</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Detailed answers to the generated questions
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Accordion type="multiple" className="w-full">
|
||||
{answers.map((answer, index) => (
|
||||
<AccordionItem
|
||||
key={answer.id}
|
||||
value={answer.id}
|
||||
className={cn(
|
||||
"mb-4 overflow-hidden rounded-lg border",
|
||||
answer.state === "inprogress"
|
||||
? "border-blue-500 shadow-sm shadow-blue-100"
|
||||
: answer.state === "done"
|
||||
? "border-green-100"
|
||||
: answer.state === "error"
|
||||
? "border-red-100"
|
||||
: "border-gray-200",
|
||||
)}
|
||||
>
|
||||
<AccordionTrigger className="px-4 py-3 hover:bg-gray-50">
|
||||
<div className="flex items-center space-x-3 text-left">
|
||||
<Badge className="shrink-0 bg-gray-700 text-white">
|
||||
Q{index + 1}
|
||||
</Badge>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium">{answer.question}</p>
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"ml-auto flex shrink-0 items-center space-x-1",
|
||||
answer.state === "inprogress"
|
||||
? "text-blue-500"
|
||||
: answer.state === "done"
|
||||
? "text-green-500"
|
||||
: answer.state === "error"
|
||||
? "text-red-500"
|
||||
: "text-gray-500",
|
||||
)}
|
||||
>
|
||||
{getStatusIcon(answer.state)}
|
||||
<span>{getStatusText(answer.state)}</span>
|
||||
</Badge>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="px-4 pb-4 pt-1">
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-md p-3",
|
||||
answer.state === "done"
|
||||
? "bg-green-50"
|
||||
: answer.state === "inprogress"
|
||||
? "bg-blue-50"
|
||||
: "bg-gray-50",
|
||||
)}
|
||||
>
|
||||
{answer.answer ? (
|
||||
<Markdown content={answer.answer} />
|
||||
) : (
|
||||
<div className="flex items-center justify-center p-4 text-gray-500">
|
||||
{answer.state === "inprogress" ? (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<span>Generating answer...</span>
|
||||
</div>
|
||||
) : (
|
||||
<span>Waiting for answer</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between">
|
||||
<div className="text-sm text-gray-500">
|
||||
{answers.filter((a) => a.state === "done").length} of{" "}
|
||||
{answers.length} questions answered
|
||||
</div>
|
||||
<Progress
|
||||
value={
|
||||
(answers.filter((a) => a.state === "done").length /
|
||||
answers.length) *
|
||||
100
|
||||
}
|
||||
className="h-2 w-1/3 bg-gray-200"
|
||||
/>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
# flake8: noqa: E402
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
import logging
|
||||
|
||||
from app.index import get_index
|
||||
from app.settings import init_settings
|
||||
from llama_index.server.services.llamacloud.generate import (
|
||||
load_to_llamacloud,
|
||||
)
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def generate_datasource():
|
||||
init_settings()
|
||||
logger.info("Generate index for the provided data")
|
||||
|
||||
index = get_index(create_if_missing=True)
|
||||
if index is None:
|
||||
raise ValueError("Index not found and could not be created")
|
||||
|
||||
load_to_llamacloud(index, logger=logger)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_datasource()
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
from llama_index.server.services.llamacloud import (
|
||||
LlamaCloudIndex,
|
||||
get_client,
|
||||
get_index,
|
||||
)
|
||||
|
||||
__all__ = ["LlamaCloudIndex", "get_client", "get_index"]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user