mirror of
https://github.com/run-llama/llama_cloud_services.git
synced 2026-07-21 12:05:23 -04:00
Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79fe1930cf | |||
| ab225c3eab | |||
| 6f1de75909 | |||
| 230ed64e41 | |||
| ef126c3a93 | |||
| 51a7534733 | |||
| 4f5d2bde13 | |||
| 3d05fe5d77 | |||
| c16ca673af | |||
| 6619034bce | |||
| c56fb5d8f7 | |||
| b407a5edb5 | |||
| e6a27d17fb | |||
| 34077fd479 | |||
| 7a68ad5a7f | |||
| 74a1b6c2f2 | |||
| 9a90ae5264 | |||
| 310c1bc105 | |||
| cd20b29299 | |||
| 0cb7aeb81c | |||
| 98db5eeeae | |||
| c21cb34ff6 | |||
| e28c7b9d92 | |||
| ee4e565604 | |||
| 6dbb089f4c | |||
| c4b694db8d | |||
| 97f428ad06 | |||
| ef92ee5408 | |||
| d094668d03 | |||
| 5bb5fc1625 | |||
| 1d57e0071d | |||
| 2a344c4f5c | |||
| ce02559b8d | |||
| e42746e372 | |||
| 3149dfd03a | |||
| e499fdbdab | |||
| e57df39248 | |||
| 09b192b98b | |||
| 13f01a0621 | |||
| cf879a1a58 | |||
| fcdf2ab63e | |||
| 083d8109c2 | |||
| 89cfc8b25f | |||
| c46e157f92 | |||
| 05d6026d37 | |||
| 8e98d5c146 | |||
| 3f311c0669 | |||
| b1a2f9d42b | |||
| 142f55c94c | |||
| 230a110e52 | |||
| 83e2b031cd | |||
| 4844e26e5c | |||
| 70a049af3c | |||
| dc11776c86 | |||
| 2448a42b90 | |||
| c75a900174 | |||
| 2fb7adfe0e | |||
| dc82270724 | |||
| d880a48dd0 | |||
| 7567e8b45e | |||
| 0d59a90151 | |||
| 98ad550b1a | |||
| b58f43ce9f | |||
| acf6adcd91 | |||
| daf6576c3c | |||
| 8caa4defa6 | |||
| 26918b8de4 | |||
| 6fb5ebe2f9 | |||
| c0aa67995b | |||
| 9f841f8328 | |||
| 99c75eece9 | |||
| 57d2586ee3 | |||
| 4280a43ec8 | |||
| 7f1082bbb2 | |||
| 57cfc45804 | |||
| 30e8913875 | |||
| 0ce6d4d7a4 | |||
| 584ba8d48e | |||
| 925805ee11 | |||
| 76fb73c971 | |||
| 6d19ea9ac0 | |||
| 90431090e9 | |||
| 6dff35b204 | |||
| e634c7978d | |||
| 7a9e99bba2 | |||
| efcdd4405b | |||
| bf3614690f | |||
| 7463e00da3 | |||
| cbe9de0c57 | |||
| a023507d42 | |||
| e48f544ddc | |||
| 4aa7ad5642 | |||
| c39cdbcd01 | |||
| 71eaa8bcc6 | |||
| 1e1cbdfc79 | |||
| cc8af4a43a | |||
| 43fbd48ab8 | |||
| 5ec66e9452 | |||
| 211521c82e | |||
| 4ddaab1efb | |||
| 53e5ce2e83 | |||
| 9f4bd1cb64 | |||
| 456863752b | |||
| c2dc34bbd6 | |||
| fcabb04baf | |||
| 8e7c32d3d6 | |||
| 7e3013d914 | |||
| 4a664c33d2 | |||
| 6d049ee2e4 | |||
| fa73e73664 | |||
| bf67ee6056 | |||
| a1abef2ee9 | |||
| a753e01d3c | |||
| 9b15065b24 | |||
| 6e4150537c | |||
| 233d715a14 | |||
| 77ac385dfe | |||
| 53b78fcd7d |
@@ -0,0 +1,11 @@
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
# and
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Build Package
|
||||
|
||||
# Build package on its own without additional pip install
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.6.1"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
# You can use PyPy versions in python-version.
|
||||
# For example, pypy-2.7 and pypy-3.8
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
python-version: ["3.9"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: poetry install
|
||||
- name: Ensure lock works
|
||||
shell: bash
|
||||
run: poetry lock
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: poetry build
|
||||
- name: Test installing built package
|
||||
shell: bash
|
||||
run: python -m pip install .
|
||||
- name: Test import
|
||||
shell: bash
|
||||
working-directory: ${{ vars.RUNNER_TEMP }}
|
||||
run: python -c "import llama_cloud_services"
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Build Package - Python
|
||||
|
||||
# Build package on its own without additional pip install
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "py/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "py/**"
|
||||
env:
|
||||
UV_VERSION: "0.7.20"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
# You can use PyPy versions in python-version.
|
||||
# For example, pypy-2.7 and pypy-3.8
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
python-version: ["3.9"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: ${{ env.UV_VERSION }}
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install
|
||||
|
||||
- name: Display Python version
|
||||
run: python --version
|
||||
|
||||
- name: Build
|
||||
working-directory: py
|
||||
run: uv build
|
||||
|
||||
- name: Test installing built package
|
||||
shell: bash
|
||||
working-directory: py
|
||||
run: |
|
||||
uv venv
|
||||
uv pip install dist/*.whl
|
||||
|
||||
- name: Test import
|
||||
working-directory: py
|
||||
run: uv run -- python -c "import llama_cloud_services"
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Build Package - TypeScript
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "ts/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "ts/**"
|
||||
|
||||
jobs:
|
||||
pre_release:
|
||||
name: Pre Release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: "ts/llama_cloud_services/.nvmrc"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ts/llama_cloud_services/
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
working-directory: ts/llama_cloud_services/
|
||||
run: pnpm run build
|
||||
@@ -0,0 +1,95 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Check repository access
|
||||
id: check-access
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Get the user who triggered the event
|
||||
case "${{ github.event_name }}" in
|
||||
"issue_comment")
|
||||
USER="${{ github.event.comment.user.login }}"
|
||||
;;
|
||||
"pull_request_review_comment")
|
||||
USER="${{ github.event.comment.user.login }}"
|
||||
;;
|
||||
"pull_request_review")
|
||||
USER="${{ github.event.review.user.login }}"
|
||||
;;
|
||||
"issues")
|
||||
USER="${{ github.event.issue.user.login }}"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Checking repository access for user: $USER"
|
||||
|
||||
# Check if user has write access to the repository
|
||||
REPO="${{ github.repository }}"
|
||||
if gh api repos/$REPO/collaborators/$USER/permission --jq '.permission' | grep -E "(admin|write)" > /dev/null 2>&1; then
|
||||
echo "User $USER has write access to the repository"
|
||||
echo "authorized=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "User $USER does not have write access to the repository"
|
||||
echo "authorized=false" >> $GITHUB_OUTPUT
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.check-access.outputs.authorized == 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
if: steps.check-access.outputs.authorized == 'true'
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_GITHUB_API_KEY }}
|
||||
|
||||
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
||||
# model: "claude-opus-4-20250514"
|
||||
|
||||
# Optional: Customize the trigger phrase (default: @claude)
|
||||
# trigger_phrase: "/claude"
|
||||
|
||||
# Optional: Trigger when specific user is assigned to an issue
|
||||
# assignee_trigger: "claude-bot"
|
||||
|
||||
# Optional: Allow Claude to run specific commands
|
||||
# Allow bash commands to be run, for things like running tests, linting, etc.
|
||||
allowed_tools: "Bash(rg:*),Bash(find:*),Bash(grep:*),Bash(pnpm:*),Bash(npm:*),Bash(uv:*),Bash(pip:*),Bash(pipx:*),Bash(make:*),Bash(cd:*),WebFetch"
|
||||
|
||||
# Optional: Add custom instructions for Claude to customize its behavior for your project
|
||||
# custom_instructions: |
|
||||
# Follow our coding standards
|
||||
# Ensure all new code has tests
|
||||
# Use TypeScript for new files
|
||||
|
||||
# Optional: Custom environment variables for Claude
|
||||
# claude_env: |
|
||||
# NODE_ENV: test
|
||||
@@ -1,14 +1,3 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
@@ -28,54 +17,25 @@ jobs:
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners
|
||||
# Consider using larger runners for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||
runs-on: "ubuntu-latest"
|
||||
timeout-minutes: 360
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["python"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ]
|
||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
languages: python
|
||||
dependency-caching: true
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
category: "/language:python"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
name: Linting
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.6.1"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
# You can use PyPy versions in python-version.
|
||||
# For example, pypy-2.7 and pypy-3.8
|
||||
matrix:
|
||||
python-version: ["3.9"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }}
|
||||
- name: Set up python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
- name: Install pre-commit
|
||||
shell: bash
|
||||
run: poetry run pip install pre-commit
|
||||
- name: Run linter
|
||||
shell: bash
|
||||
run: poetry run make lint
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Lint - Python
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
UV_VERSION: "0.7.20"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
# You can use PyPy versions in python-version.
|
||||
# For example, pypy-2.7 and pypy-3.8
|
||||
matrix:
|
||||
python-version: ["3.9"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }}
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: ${{ env.UV_VERSION }}
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install ${{ matrix.python-version }}
|
||||
|
||||
- name: Run linter
|
||||
shell: bash
|
||||
working-directory: py
|
||||
run: uv run -- pre-commit run -a
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Lint - TypeScript
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "ts/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "ts/**"
|
||||
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_REMOTE_ONLY: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: "ts/llama_cloud_services/.nvmrc"
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
- name: Run lint
|
||||
working-directory: ts/llama_cloud_services/
|
||||
run: pnpm run lint
|
||||
- name: Run Prettier
|
||||
working-directory: ts/llama_cloud_services/
|
||||
run: pnpm run format
|
||||
@@ -1,75 +0,0 @@
|
||||
name: Publish llama-parse to PyPI / GitHub
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.6.1"
|
||||
PYTHON_VERSION: "3.9"
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish to PyPI
|
||||
if: github.repository == 'run-llama/llama_cloud_services'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: pip install -e .
|
||||
|
||||
- name: Build and publish llama-cloud-services
|
||||
uses: JRubics/poetry-publish@v2.1
|
||||
with:
|
||||
pypi_token: ${{ secrets.LLAMA_PARSE_PYPI_TOKEN }}
|
||||
poetry_install_options: "--without dev"
|
||||
|
||||
- name: Build and publish llama-parse
|
||||
uses: JRubics/poetry-publish@v2.1
|
||||
with:
|
||||
working_directory: "llama_parse"
|
||||
pypi_token: ${{ secrets.LLAMA_PARSE_PYPI_TOKEN }}
|
||||
poetry_install_options: "--without dev"
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Get Asset name
|
||||
run: |
|
||||
export PKG=$(ls dist/ | grep tar)
|
||||
set -- $PKG
|
||||
echo "name=$1" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Release Asset (sdist) to GitHub
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: dist/${{ env.name }}
|
||||
asset_name: ${{ env.name }}
|
||||
asset_content_type: application/zip
|
||||
@@ -0,0 +1,66 @@
|
||||
name: Publish Release - Python
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
UV_VERSION: "0.7.20"
|
||||
|
||||
jobs:
|
||||
build-n-publish:
|
||||
name: Build and publish to PyPI
|
||||
if: github.repository == 'run-llama/llama_cloud_services'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: ${{ env.UV_VERSION }}
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install
|
||||
|
||||
- name: Display Python version
|
||||
run: python --version
|
||||
|
||||
- name: Build
|
||||
working-directory: py
|
||||
run: uv build
|
||||
|
||||
- name: Test installing built package
|
||||
shell: bash
|
||||
working-directory: py
|
||||
run: |
|
||||
uv venv
|
||||
uv pip install dist/*.whl
|
||||
|
||||
- name: Publish package
|
||||
shell: bash
|
||||
working-directory: py
|
||||
run: uv publish --token ${{ secrets.LLAMA_PARSE_PYPI_TOKEN }}
|
||||
|
||||
- name: Build and publish llama-parse
|
||||
working-directory: py/llama_parse/
|
||||
run: |
|
||||
uv build
|
||||
uv publish --token ${{ secrets.LLAMA_PARSE_PYPI_TOKEN }}
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }} - LlamaCloud Services PY
|
||||
artifacts: "py/**/dist/*"
|
||||
generateReleaseNotes: true
|
||||
draft: false
|
||||
prerelease: false
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Publish Release - TypeScript
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "llama-cloud-services@*"
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: "ts/llama_cloud_services/.nvmrc"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Run Build
|
||||
working-directory: ts/llama_cloud_services/
|
||||
run: pnpm build
|
||||
|
||||
- name: Build tarball
|
||||
run: |
|
||||
pnpm pack
|
||||
working-directory: ts/llama_cloud_services
|
||||
|
||||
- name: Setup npm authentication
|
||||
run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Release
|
||||
working-directory: ts/llama_cloud_services
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: pnpm publish --access public --no-git-checks
|
||||
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "ts/llama_cloud_services/llama-cloud-services*.tgz"
|
||||
name: Release ${{ github.ref }} - LlamaCloud Services TS
|
||||
bodyFile: "ts/llama_cloud_services/CHANGELOG.md"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Test end-to-end - Python
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "py/**"
|
||||
|
||||
env:
|
||||
UV_VERSION: "0.7.20"
|
||||
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
|
||||
|
||||
jobs:
|
||||
test_e2e:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
# You can use PyPy versions in python-version.
|
||||
# For example, pypy-2.7 and pypy-3.8
|
||||
matrix:
|
||||
python-version: ["3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: ${{ env.UV_VERSION }}
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install ${{ matrix.python-version }} && uv python pin ${{ matrix.python-version }}
|
||||
|
||||
- name: Run Tests
|
||||
working-directory: py
|
||||
run: uv run pytest unit_tests/ tests/ -v
|
||||
|
||||
- name: Remove virtual environment
|
||||
working-directory: py
|
||||
run: rm -rf .venv/
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Test - Python
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "py/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "py/**"
|
||||
|
||||
env:
|
||||
UV_VERSION: "0.7.20"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
# You can use PyPy versions in python-version.
|
||||
# For example, pypy-2.7 and pypy-3.8
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
version: ${{ env.UV_VERSION }}
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install ${{ matrix.python-version }} && uv python pin ${{ matrix.python-version }}
|
||||
|
||||
- name: Run Tests
|
||||
working-directory: py
|
||||
run: uv run pytest unit_tests/ -v
|
||||
|
||||
- name: Remove virtual environment
|
||||
working-directory: py
|
||||
run: rm -rf .venv/
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Lint - TypeScript
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "ts/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "ts/**"
|
||||
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_REMOTE_ONLY: true
|
||||
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test - TypeScript
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: "ts/llama_cloud_services/.nvmrc"
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
- name: Run Build
|
||||
working-directory: ts/llama_cloud_services/
|
||||
run: pnpm build
|
||||
- name: Run Tests
|
||||
working-directory: ts/llama_cloud_services/
|
||||
run: pnpm test
|
||||
- name: Run e2e tests
|
||||
working-directory: ts/e2e-tests/
|
||||
run: pnpm test
|
||||
@@ -1,40 +0,0 @@
|
||||
name: Unit Testing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.6.1"
|
||||
LLAMA_CLOUD_API_KEY: ${{ secrets.LLAMA_CLOUD_API_KEY }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
# You can use PyPy versions in python-version.
|
||||
# For example, pypy-2.7 and pypy-3.8
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: poetry install --with dev
|
||||
- name: Run testing
|
||||
env:
|
||||
CI: true
|
||||
shell: bash
|
||||
run: poetry run pytest tests
|
||||
@@ -5,3 +5,7 @@ __pycache__/
|
||||
.idea
|
||||
.env*
|
||||
.ipynb_checkpoints*
|
||||
*_cache/
|
||||
node_modules/
|
||||
.turbo/
|
||||
dist/
|
||||
|
||||
@@ -15,25 +15,26 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: mixed-line-ending
|
||||
- id: trailing-whitespace
|
||||
exclude: ^ts/llama_cloud_services/src/client/
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.1.5
|
||||
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
exclude: ".*poetry.lock"
|
||||
exclude: ".*uv.lock"
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.10.1
|
||||
hooks:
|
||||
- id: black-jupyter
|
||||
name: black-src
|
||||
alias: black
|
||||
exclude: ".*poetry.lock"
|
||||
exclude: ".*uv.lock"
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.0.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
exclude: ^tests/
|
||||
exclude: ^py/tests|^py/unit_tests
|
||||
additional_dependencies:
|
||||
[
|
||||
"types-requests",
|
||||
@@ -63,13 +64,13 @@ repos:
|
||||
rev: v3.0.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: poetry.lock
|
||||
exclude: ^(uv.lock|ts/llama_cloud_services/pnpm-lock.yaml|ts/e2e-tests)
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.6
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies: [tomli]
|
||||
exclude: ^(poetry.lock|examples)
|
||||
exclude: ^(uv.lock|docs|ts|examples|pnpm-lock.yaml)
|
||||
args:
|
||||
[
|
||||
"--ignore-words-list",
|
||||
@@ -84,6 +85,6 @@ repos:
|
||||
rev: v0.23.1
|
||||
hooks:
|
||||
- id: toml-sort-fix
|
||||
exclude: ".*poetry.lock"
|
||||
exclude: ".*uv.lock"
|
||||
|
||||
exclude: .github/ISSUE_TEMPLATE
|
||||
exclude: ^(.github/ISSUE_TEMPLATE|ts/llama_cloud_services/src/client|pnpm-lock.yaml)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Python
|
||||
|
||||
## Installation
|
||||
|
||||
This project uses uv. Create a virtual environment, and run `uv sync`
|
||||
|
||||
## Versioning (Maintainers only)
|
||||
|
||||
Before merging your changes, make sure to bump the versions.
|
||||
|
||||
Make a version bump to `pyproject.toml`. If the underlying dependency on the llamacloud platform OpenAPI
|
||||
sdk needs bumping, make sure to bring that in as well. If updating dependencies, run `uv lock`.
|
||||
|
||||
The legacy `llama_parse` package re-exports some of `llama_cloud_services` in the old namespace. The
|
||||
versions need to be kept consistent to sidecar it with `llama_cloud_services`. Bump it's version in `llama_parse/pyproject.toml`, and also bump it's dependency version of `llama-cloud-services` to match.
|
||||
|
||||
**Note**: Don't worry about updating the `llama_parse/poetry.lock` file when bumping versions. The GitHub action will automatically run `poetry lock` for the llama_parse package during the build process (though it doesn't commit the updated lockfile back to the repo).
|
||||
|
||||
You can also do this with `./scripts/version-bump.py set 0.x.x` if you have `uv` installed.
|
||||
|
||||
Once the change is merged, push a tag `git tag -a v0.x.x -m 0.x.x` and `git push origin 0.x.x`.
|
||||
|
||||
This tagging step can be done with `./scripts/version-bump tag`.
|
||||
|
||||
# Typescript
|
||||
|
||||
## Installation
|
||||
|
||||
...
|
||||
|
||||
## Versioning
|
||||
|
||||
...
|
||||
@@ -10,7 +10,8 @@ This includes:
|
||||
|
||||
- [LlamaParse](./parse.md) - A GenAI-native document parser that can parse complex document data for any downstream LLM use case (Agents, RAG, data processing, etc.).
|
||||
- [LlamaReport (beta/invite-only)](./report.md) - A prebuilt agentic report builder that can be used to build reports from a variety of data sources.
|
||||
- [LlamaExtract (beta/invite-only)](./extract.md) - A prebuilt agentic data extractor that can be used to transform data into a structured JSON representation.
|
||||
- [LlamaExtract](./extract.md) - A prebuilt agentic data extractor that can be used to transform data into a structured JSON representation.
|
||||
- [LlamaCloud Index](./index.md) - A widely customizable and fully automated document ingestion pipeline that also serves retrieval purposes.
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -25,18 +26,27 @@ Then, get your API key from [LlamaCloud](https://cloud.llamaindex.ai/).
|
||||
Then, you can use the services in your code:
|
||||
|
||||
```python
|
||||
from llama_cloud_services import LlamaParse, LlamaReport, LlamaExtract
|
||||
from llama_cloud_services import (
|
||||
LlamaParse,
|
||||
LlamaReport,
|
||||
LlamaExtract,
|
||||
LlamaCloudIndex,
|
||||
)
|
||||
|
||||
parser = LlamaParse(api_key="YOUR_API_KEY")
|
||||
report = LlamaReport(api_key="YOUR_API_KEY")
|
||||
extract = LlamaExtract(api_key="YOUR_API_KEY")
|
||||
index = LlamaCloudIndex(
|
||||
"my_first_index", project_name="default", api_key="YOUR_API_KEY"
|
||||
)
|
||||
```
|
||||
|
||||
See the quickstart guides for each service for more information:
|
||||
|
||||
- [LlamaParse](./parse.md)
|
||||
- [LlamaReport (beta/invite-only)](./report.md)
|
||||
- [LlamaExtract (beta/invite-only)](./extract.md)
|
||||
- [LlamaExtract](./extract.md)
|
||||
- [LlamaCloud Index](./index.md)
|
||||
|
||||
## Switch to EU SaaS 🇪🇺
|
||||
|
||||
@@ -55,6 +65,12 @@ from llama_cloud_services import (
|
||||
parser = LlamaParse(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
report = LlamaReport(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
extract = LlamaExtract(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
index = LlamaCloudIndex(
|
||||
"my_first_index",
|
||||
project_name="default",
|
||||
api_key="YOUR_API_KEY",
|
||||
base_url=EU_BASE_URL,
|
||||
)
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# LlamaCloud Services Examples - Python
|
||||
|
||||
In this folder you will find several TypeScript end-to-end applications that contain examples regarding:
|
||||
|
||||
- [LlamaParse](./parse/)
|
||||
- [LlamaCloud Index](./index/)
|
||||
|
||||
Follow the instructions in each example folder to get started!
|
||||
@@ -0,0 +1,122 @@
|
||||
# LlamaExtract Demo
|
||||
|
||||
A TypeScript demo application showcasing the power of **LlamaExract** - a structured data extraction agentic service from [LlamaCloud](https://cloud.llamaindex.ai). This demo allows you to extract structured information from scientific papers and get them into a nice markdown format.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Start the Demo](#start-the-demo)
|
||||
- [Development Mode](#development-mode)
|
||||
- [Build the Project](#build-the-project)
|
||||
- [Code Quality](#code-quality)
|
||||
- [Quick Commands Reference](#quick-commands-reference)
|
||||
- [How It Works](#how-it-works)
|
||||
- [API Dependencies](#api-dependencies)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Common Issues](#common-issues)
|
||||
- [License](#license)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## Features
|
||||
|
||||
- 📄 **Structured Data Extraction**: Extract data from your files effortlessly, and structure them the way you want!
|
||||
- 🤖 **Markdown Rendering**: Generate markdown directly from your extracted data
|
||||
- 🎨 **Beautiful CLI**: Styled console interface with colors and ASCII art
|
||||
- ⚡ **Fast Development**: Hot reload support with watch mode
|
||||
- 🛠️ **TypeScript**: Full TypeScript support with strict type checking
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js (version 18 or higher)
|
||||
- pnpm package manager
|
||||
- LlamaCloud API key
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/run-llama/llama_cloud_services
|
||||
cd lama_cloud_services/examples-ts/extract/
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Set up your environment variables:
|
||||
|
||||
```bash
|
||||
# Add your API key to your environment
|
||||
export LLAMA_CLOUD_API_KEY="your-llamacloud-api-key"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Start the Demo
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
The application will display a welcome screen and prompt you to enter the path to a document you'd like to process.
|
||||
|
||||
### Development Mode
|
||||
|
||||
For development with hot reload:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Build the Project
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
|
||||
Format code:
|
||||
|
||||
```bash
|
||||
npm run format
|
||||
```
|
||||
|
||||
Lint code:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Document Input**: Enter the path to your document when prompted
|
||||
2. **Parsing**: LlamaExtract, based on the schema you can find [here](./src/schema.ts), processes the document and extracts structured data
|
||||
3. **Markdown Rendering**: The extracted content is rendered into beautiful markdown
|
||||
4. **Results**: View the results directly in your terminal
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Module Resolution Errors**: Ensure you're using Node.js 18+ and have all dependencies installed
|
||||
2. **API Key Issues**: Verify your LlamaCloud API key is correctly set
|
||||
3. **File Path Errors**: Use absolute paths or ensure relative paths are correct from the project root
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see the [LICENSE](../../LICENSE) file for details.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Run `npm run format` and `npm run lint`
|
||||
5. Submit a pull request
|
||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
||||
plugins: { js },
|
||||
extends: ["js/recommended"],
|
||||
languageOptions: { globals: globals.browser },
|
||||
},
|
||||
tseslint.configs.recommended,
|
||||
]);
|
||||
Generated
+3276
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "llama-extract-demo",
|
||||
"version": "0.1.0",
|
||||
"description": "Demo for LlamaExtract in TypeScript",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"There are no tests\"",
|
||||
"start": "npm exec tsx src/index.ts",
|
||||
"lint": "eslint ./src/",
|
||||
"format": "prettier --write ./src/",
|
||||
"build": "tsc",
|
||||
"dev": "npm exec tsx --watch src/index.ts"
|
||||
},
|
||||
"author": "LlamaIndex",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cli-markdown": "^3.5.1",
|
||||
"consola": "^3.4.2",
|
||||
"figlet": "^1.8.2",
|
||||
"llama-cloud-services": "file:../../ts/llama_cloud_services",
|
||||
"marked": "^15.0.12",
|
||||
"marked-terminal": "^7.3.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@types/figlet": "^1.7.0",
|
||||
"@types/marked-terminal": "^6.1.1",
|
||||
"@types/node": "^24.2.0",
|
||||
"eslint": "^9.32.0",
|
||||
"globals": "^16.3.0",
|
||||
"jiti": "^2.5.1",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.2",
|
||||
"typescript-eslint": "^8.39.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { LlamaExtract, ExtractConfig } from "llama-cloud-services";
|
||||
import cliMarkdown from "cli-markdown";
|
||||
import { logger } from "./logger";
|
||||
import pc from "picocolors";
|
||||
import { consoleInput, renderLogo } from "./utils";
|
||||
import { dataSchema } from "./schema";
|
||||
import { renderMarkdown, ResearchData } from "./markdown";
|
||||
|
||||
export async function main(): Promise<number> {
|
||||
const extractClient = new LlamaExtract(
|
||||
process.env.LLAMA_CLOUD_API_KEY!,
|
||||
"https://api.cloud.llamaindex.ai",
|
||||
);
|
||||
await renderLogo();
|
||||
logger.log(
|
||||
`Welcome to ${pc.bold(
|
||||
pc.magentaBright("LlamaExtract Demo✨"),
|
||||
)}, our demo for ${pc.bold(pc.green("LlamaExtract"))}, a ${pc.bold(
|
||||
pc.cyan("LlamaCloud☁️"),
|
||||
)} (https://cloud.llamaindex.ai) product!.\nIn this demo we are going to try extracting relevant information ${pc.bold(
|
||||
pc.yellowBright("from scientific papers"),
|
||||
)}. Type the path to the paper you would like to process below👇\nIf you wish to exit, just type ${pc.bold(
|
||||
pc.gray("quit"),
|
||||
)}.\n`,
|
||||
);
|
||||
while (true) {
|
||||
const userInput = await consoleInput();
|
||||
if (userInput.toLowerCase() == "quit") {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
const generatedData = await extractClient.extract(
|
||||
dataSchema,
|
||||
{} as ExtractConfig,
|
||||
userInput,
|
||||
);
|
||||
const research = renderMarkdown(generatedData?.data as ResearchData); // Added await here
|
||||
logger.log(`${pc.bold(pc.cyan("Extracted information:✨"))}:\n`);
|
||||
logger.log(cliMarkdown(research));
|
||||
} catch (error) {
|
||||
logger.error(`Error processing file: ${error}`);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createConsola } from "consola";
|
||||
import type { ConsolaInstance } from "consola";
|
||||
|
||||
export const logger: ConsolaInstance = createConsola({
|
||||
formatOptions: {
|
||||
date: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,172 @@
|
||||
type Author = {
|
||||
name: string;
|
||||
affiliation?: string;
|
||||
email?: string;
|
||||
};
|
||||
|
||||
type Methodology = {
|
||||
approach?: string;
|
||||
participants?: string;
|
||||
methods?: string[];
|
||||
};
|
||||
|
||||
type Result = {
|
||||
finding?: string;
|
||||
significance?: string;
|
||||
supportingData?: string;
|
||||
};
|
||||
|
||||
type Reference = {
|
||||
title: string;
|
||||
authors: string;
|
||||
year?: string;
|
||||
relevance?: string;
|
||||
};
|
||||
|
||||
type Discussion = {
|
||||
implications?: string[];
|
||||
limitations?: string[];
|
||||
futureWork?: string[];
|
||||
};
|
||||
|
||||
type Publication = {
|
||||
journal?: string;
|
||||
year: string;
|
||||
doi?: string;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
export type ResearchData = {
|
||||
title: string;
|
||||
authors: Author[];
|
||||
abstract: string;
|
||||
keywords?: string[];
|
||||
mainFindings: string[];
|
||||
methodology?: Methodology;
|
||||
results?: Result[];
|
||||
discussion?: Discussion;
|
||||
references?: Reference[];
|
||||
publication?: Publication;
|
||||
};
|
||||
|
||||
export function renderMarkdown(data: ResearchData): string {
|
||||
const {
|
||||
title,
|
||||
authors,
|
||||
abstract,
|
||||
keywords,
|
||||
mainFindings,
|
||||
methodology,
|
||||
results,
|
||||
discussion,
|
||||
references,
|
||||
publication,
|
||||
} = data;
|
||||
|
||||
const md: string[] = [];
|
||||
|
||||
md.push(`# ${title}\n`);
|
||||
|
||||
// Authors
|
||||
md.push(`## Authors`);
|
||||
md.push(
|
||||
authors
|
||||
.map(
|
||||
(author) =>
|
||||
`- **${author.name}**${
|
||||
author.affiliation ? `, *${author.affiliation}*` : ""
|
||||
}${author.email ? ` (${author.email})` : ""}`,
|
||||
)
|
||||
.join("\n"),
|
||||
);
|
||||
|
||||
// Abstract
|
||||
md.push(`\n## Abstract\n${abstract}`);
|
||||
|
||||
// Keywords
|
||||
if (keywords && keywords.length > 0) {
|
||||
md.push(`\n## Keywords\n${keywords.map((k) => `- ${k}`).join("\n")}`);
|
||||
}
|
||||
|
||||
// Main Findings
|
||||
md.push(
|
||||
`\n## Main Findings\n${mainFindings.map((f) => `- ${f}`).join("\n")}`,
|
||||
);
|
||||
|
||||
// Methodology
|
||||
if (methodology) {
|
||||
md.push(`\n## Methodology`);
|
||||
if (methodology.approach) md.push(`**Approach:** ${methodology.approach}`);
|
||||
if (methodology.participants)
|
||||
md.push(`**Participants:** ${methodology.participants}`);
|
||||
if (methodology.methods?.length) {
|
||||
md.push(
|
||||
`**Methods:**\n${methodology.methods.map((m) => `- ${m}`).join("\n")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Results
|
||||
if (results?.length) {
|
||||
md.push(`\n## Results`);
|
||||
results.forEach((result, i) => {
|
||||
md.push(`\n### Result ${i + 1}`);
|
||||
if (result.finding) md.push(`- **Finding:** ${result.finding}`);
|
||||
if (result.significance)
|
||||
md.push(`- **Significance:** ${result.significance}`);
|
||||
if (result.supportingData)
|
||||
md.push(`- **Supporting Data:** ${result.supportingData}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Discussion
|
||||
if (discussion) {
|
||||
md.push(`\n## Discussion`);
|
||||
if (discussion.implications?.length) {
|
||||
md.push(
|
||||
`### Implications\n${discussion.implications
|
||||
.map((d) => `- ${d}`)
|
||||
.join("\n")}`,
|
||||
);
|
||||
}
|
||||
if (discussion.limitations?.length) {
|
||||
md.push(
|
||||
`### Limitations\n${discussion.limitations
|
||||
.map((d) => `- ${d}`)
|
||||
.join("\n")}`,
|
||||
);
|
||||
}
|
||||
if (discussion.futureWork?.length) {
|
||||
md.push(
|
||||
`### Future Work\n${discussion.futureWork
|
||||
.map((d) => `- ${d}`)
|
||||
.join("\n")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// References
|
||||
if (references?.length) {
|
||||
md.push(`\n## References`);
|
||||
references.forEach((ref, i) => {
|
||||
md.push(
|
||||
`\n**[${i + 1}]** ${ref.title} — *${ref.authors}*${
|
||||
ref.year ? ` (${ref.year})` : ""
|
||||
}`,
|
||||
);
|
||||
if (ref.relevance) md.push(`> ${ref.relevance}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Publication Info
|
||||
if (publication) {
|
||||
md.push(`\n## Publication`);
|
||||
if (publication.journal) md.push(`- **Journal:** ${publication.journal}`);
|
||||
if (publication.year) md.push(`- **Year:** ${publication.year}`);
|
||||
if (publication.doi) md.push(`- **DOI:** ${publication.doi}`);
|
||||
if (publication.url)
|
||||
md.push(`- **URL:** [${publication.url}](${publication.url})`);
|
||||
}
|
||||
|
||||
return md.join("\n");
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
export const dataSchema = {
|
||||
type: "object",
|
||||
required: ["title", "authors", "abstract", "mainFindings"],
|
||||
properties: {
|
||||
title: {
|
||||
type: "string",
|
||||
description: "The full title of the research paper",
|
||||
},
|
||||
authors: {
|
||||
type: "array",
|
||||
description: "List of all authors of the paper",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
description: "Full name of the author",
|
||||
},
|
||||
affiliation: {
|
||||
type: "string",
|
||||
description:
|
||||
"Institution or organization the author is affiliated with",
|
||||
},
|
||||
email: {
|
||||
type: "string",
|
||||
description: "Contact email of the author if provided",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
abstract: {
|
||||
type: "string",
|
||||
description: "Complete abstract or summary of the paper",
|
||||
},
|
||||
keywords: {
|
||||
type: "array",
|
||||
description:
|
||||
"Key terms and phrases that describe the paper's main topics",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
mainFindings: {
|
||||
type: "array",
|
||||
description: "Key findings, conclusions, or contributions of the paper",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
methodology: {
|
||||
type: "object",
|
||||
description: "Research methods and approaches used",
|
||||
properties: {
|
||||
approach: {
|
||||
type: "string",
|
||||
description: "Overall research approach or study design",
|
||||
},
|
||||
participants: {
|
||||
type: "string",
|
||||
description: "Description of study participants or data sources",
|
||||
},
|
||||
methods: {
|
||||
type: "array",
|
||||
description: "Specific methods, techniques, or tools used",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
results: {
|
||||
type: "array",
|
||||
description: "Main results and outcomes of the research",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
finding: {
|
||||
type: "string",
|
||||
description: "Description of the specific result or finding",
|
||||
},
|
||||
significance: {
|
||||
type: "string",
|
||||
description:
|
||||
"Statistical significance or importance of the finding",
|
||||
},
|
||||
supportingData: {
|
||||
type: "string",
|
||||
description: "Relevant statistics, measurements, or data points",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
discussion: {
|
||||
type: "object",
|
||||
properties: {
|
||||
implications: {
|
||||
type: "array",
|
||||
description: "Theoretical or practical implications of the findings",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
limitations: {
|
||||
type: "array",
|
||||
description: "Study limitations or constraints",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
futureWork: {
|
||||
type: "array",
|
||||
description: "Suggested future research directions",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
references: {
|
||||
type: "array",
|
||||
description:
|
||||
"Key papers cited that are crucial to understanding this work",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
title: {
|
||||
type: "string",
|
||||
description: "Title of the cited paper",
|
||||
},
|
||||
authors: {
|
||||
type: "string",
|
||||
description: "Authors of the cited paper",
|
||||
},
|
||||
year: {
|
||||
type: "string",
|
||||
description: "Publication year",
|
||||
},
|
||||
relevance: {
|
||||
type: "string",
|
||||
description: "Why this reference is important to the current paper",
|
||||
},
|
||||
},
|
||||
required: ["title", "authors"],
|
||||
},
|
||||
},
|
||||
publication: {
|
||||
type: "object",
|
||||
properties: {
|
||||
journal: {
|
||||
type: "string",
|
||||
description: "Name of the journal or conference",
|
||||
},
|
||||
year: {
|
||||
type: "string",
|
||||
description: "Year of publication",
|
||||
},
|
||||
doi: {
|
||||
type: "string",
|
||||
description: "Digital Object Identifier (DOI) of the paper",
|
||||
},
|
||||
url: {
|
||||
type: "string",
|
||||
description: "URL where the paper can be accessed",
|
||||
},
|
||||
},
|
||||
required: ["year"],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
declare module "cli-markdown" {
|
||||
function cliMarkdown(input: string): string;
|
||||
export default cliMarkdown;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as readline from "readline/promises";
|
||||
import figlet from "figlet";
|
||||
import pc from "picocolors";
|
||||
|
||||
export async function renderLogo(): Promise<void> {
|
||||
const logoText = figlet.textSync("Extract Demo", {
|
||||
font: "ANSI Shadow",
|
||||
horizontalLayout: "default",
|
||||
verticalLayout: "default",
|
||||
width: 100,
|
||||
whitespaceBreak: true,
|
||||
});
|
||||
|
||||
// Add some styling with picocolors
|
||||
const styledLogo = pc.bold(pc.redBright(logoText));
|
||||
|
||||
// Add some padding/margin
|
||||
console.log("\n");
|
||||
console.log(styledLogo);
|
||||
console.log(pc.gray("─".repeat(60)));
|
||||
console.log("\n");
|
||||
}
|
||||
|
||||
export async function consoleInput(): Promise<string> {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const answer = await rl.question("Path to your file: ");
|
||||
rl.close();
|
||||
return answer;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
# LlamaCloud Index Demo
|
||||
|
||||
A TypeScript demo application showcasing the power of **LlamaCloud Index** - a fully automated document ingestion and retrieval serviced offered within [LlamaCloud](https://cloud.llamaindex.ai). This demo allows you to ask questions, retrieve relevant contextual information and generate AI-powered responses using OpenAI's GPT models.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Start the Demo](#start-the-demo)
|
||||
- [Development Mode](#development-mode)
|
||||
- [Build the Project](#build-the-project)
|
||||
- [Code Quality](#code-quality)
|
||||
- [Quick Commands Reference](#quick-commands-reference)
|
||||
- [How It Works](#how-it-works)
|
||||
- [API Dependencies](#api-dependencies)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Common Issues](#common-issues)
|
||||
- [License](#license)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## Features
|
||||
|
||||
- 🤖 **RAG**: Simple-yet-effective Retrieval Augmented Generation pipeline built on top of LlamaCloud Index and OpenAI
|
||||
- 🎨 **Beautiful CLI**: Styled console interface with colors and ASCII art
|
||||
- ⚡ **Fast Development**: Hot reload support with watch mode
|
||||
- 🛠️ **TypeScript**: Full TypeScript support with strict type checking
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js (version 18 or higher)
|
||||
- pnpm package manager
|
||||
- OpenAI API key
|
||||
- LlamaCloud API key
|
||||
- An existing LlamaCloud Index pipeline
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/run-llama/llama_cloud_services
|
||||
cd lama_cloud_services/examples-ts/index/
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. Set up your environment variables:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your-openai-api-key"
|
||||
export LLAMA_CLOUD_API_KEY="your-llamacloud-api-key"
|
||||
export PIPELINE_NAME="your-pipeline-name"
|
||||
```
|
||||
|
||||
4. Or write them into a `.env` file:
|
||||
|
||||
```env
|
||||
OPENAI_API_KEY="your-openai-api-key"
|
||||
LLAMA_CLOUD_API_KEY="your-llamacloud-api-key"
|
||||
PIPELINE_NAME="your-pipeline-name"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Start the Demo
|
||||
|
||||
```bash
|
||||
pnpm run start
|
||||
```
|
||||
|
||||
The application will display a welcome screen and prompt you to start chatting!
|
||||
|
||||
### Development Mode
|
||||
|
||||
For development with hot reload:
|
||||
|
||||
```bash
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
### Build the Project
|
||||
|
||||
```bash
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
|
||||
Format code:
|
||||
|
||||
```bash
|
||||
pnpm run format
|
||||
```
|
||||
|
||||
Lint code:
|
||||
|
||||
```bash
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Message Input**: Enter a message
|
||||
2. **Retrieval**: Several nodes are retrieved from the LlamaCloud index you specified
|
||||
3. **AI Response Generation**: The retrieved information is passed on to the AI model, along with its relevance score, and a reply to your original message is generated starting from that.
|
||||
4. **Results**: View the AI-generated summary in your terminal
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Module Resolution Errors**: Ensure you're using Node.js 18+ and have all dependencies installed
|
||||
2. **API Key Issues**: Verify your OpenAI and LlamaCloud API keys are correctly set
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see the [LICENSE](../../LICENSE) file for details.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Run `pnpm run format` and `pnpm run lint`
|
||||
5. Submit a pull request
|
||||
@@ -0,0 +1,15 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
||||
plugins: { js },
|
||||
extends: ["js/recommended"],
|
||||
languageOptions: { globals: globals.browser },
|
||||
},
|
||||
{ files: ["**/*.js"], languageOptions: { sourceType: "script" } },
|
||||
tseslint.configs.recommended,
|
||||
]);
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "llama-chat",
|
||||
"version": "0.1.0",
|
||||
"description": "Demo for LlamaCloud Index in TypeScript",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"There are no tests\"",
|
||||
"start": "pnpm exec tsx src/index.ts",
|
||||
"lint": "eslint ./src/",
|
||||
"format": "prettier --write ./src/",
|
||||
"build": "tsc",
|
||||
"dev": "pnpm exec tsx --watch src/index.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"ai",
|
||||
"rag",
|
||||
"retrieval",
|
||||
"pipeline",
|
||||
"llms",
|
||||
"chatbot"
|
||||
],
|
||||
"author": "LlamaIndex",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.12.4",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@types/figlet": "^1.7.0",
|
||||
"@types/node": "^24.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
||||
"@typescript-eslint/parser": "^8.38.0",
|
||||
"eslint": "^9.32.0",
|
||||
"globals": "^16.3.0",
|
||||
"jiti": "^2.5.1",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.38.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^1.3.23",
|
||||
"ai": "^4.3.19",
|
||||
"consola": "^3.4.2",
|
||||
"dotenv": "^17.2.1",
|
||||
"figlet": "^1.8.2",
|
||||
"llama-cloud-services": "link:../../ts/llama_cloud_services",
|
||||
"picocolors": "^1.1.1"
|
||||
}
|
||||
}
|
||||
Generated
+1770
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
import { LlamaCloudIndex } from "llama-cloud-services";
|
||||
import { logger } from "./logger";
|
||||
import pc from "picocolors";
|
||||
import {
|
||||
consoleInput,
|
||||
retrievalAugmentedGeneration,
|
||||
renderLogo,
|
||||
} from "./utils";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export async function main(): Promise<number> {
|
||||
const index = new LlamaCloudIndex({
|
||||
name: process.env.PIPELINE_NAME as string,
|
||||
projectName: "Default",
|
||||
apiKey: process.env.LLAMA_CLOUD_API_KEY, // can provide API-key in the constructor or in the env
|
||||
});
|
||||
const retriever = index.asRetriever({
|
||||
similarityTopK: 5,
|
||||
});
|
||||
await renderLogo();
|
||||
logger.log(
|
||||
`Welcome to ${pc.bold(
|
||||
pc.magentaBright("✨LlamaChat✨"),
|
||||
)}, our demo for ${pc.bold(pc.green("Index🦙"))}, a ${pc.bold(
|
||||
pc.cyan("LlamaCloud☁️"),
|
||||
)} (https://cloud.llamaindex.ai) product!.\nType a question below, and you will get an answer!👇\nIf you wish to exit, just type ${pc.bold(
|
||||
pc.gray("quit"),
|
||||
)}.\n`,
|
||||
);
|
||||
while (true) {
|
||||
const userInput = await consoleInput();
|
||||
if (userInput.toLowerCase() == "quit") {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
const nodes = await retriever.retrieve(userInput);
|
||||
const summary = await retrievalAugmentedGeneration(nodes, userInput);
|
||||
logger.log(`${pc.bold(pc.magentaBright("LlamaChat✨:"))}\n${summary}`);
|
||||
} catch (error) {
|
||||
logger.error(`Error processing your request: ${error}`);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createConsola } from "consola";
|
||||
import type { ConsolaInstance } from "consola";
|
||||
|
||||
export const logger: ConsolaInstance = createConsola({
|
||||
formatOptions: {
|
||||
date: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { generateText } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { NodeWithScore, MetadataMode } from "llamaindex";
|
||||
import * as readline from "readline/promises";
|
||||
import figlet from "figlet";
|
||||
import pc from "picocolors";
|
||||
|
||||
export async function renderLogo(): Promise<void> {
|
||||
const logoText = figlet.textSync("LlamaChat", {
|
||||
font: "ANSI Shadow",
|
||||
horizontalLayout: "default",
|
||||
verticalLayout: "default",
|
||||
width: 100,
|
||||
whitespaceBreak: true,
|
||||
});
|
||||
|
||||
// Add some styling with picocolors
|
||||
const styledLogo = pc.bold(pc.yellowBright(logoText));
|
||||
|
||||
// Add some padding/margin
|
||||
console.log("\n");
|
||||
console.log(styledLogo);
|
||||
console.log(pc.gray("─".repeat(60)));
|
||||
console.log("\n");
|
||||
}
|
||||
|
||||
export async function consoleInput(): Promise<string> {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const answer = await rl.question(pc.cyanBright("You✨:"));
|
||||
rl.close();
|
||||
return answer;
|
||||
}
|
||||
|
||||
export async function retrievalAugmentedGeneration(
|
||||
nodes: NodeWithScore[],
|
||||
prompt: string,
|
||||
): Promise<string> {
|
||||
let mainText: string = "";
|
||||
|
||||
for (const node of nodes) {
|
||||
mainText += `\t{information: '${node.node.getContent(
|
||||
MetadataMode.ALL,
|
||||
)}', relevanceScore: '${node.score ?? "no score"}'}\n`;
|
||||
}
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai("gpt-4.1"),
|
||||
prompt: `[\n${mainText}\n]\n\nBased on the information you are given and on the relevance score of that (where -1 means no score available), answer to this user prompt: '${prompt}'`,
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"types": ["node"],
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
# LlamaParse Demo
|
||||
|
||||
A TypeScript demo application showcasing the power of **LlamaParse** - an intelligent document parsing service from [LlamaCloud](https://cloud.llamaindex.ai). This demo allows you to parse various document formats and generate AI-powered summaries using OpenAI's GPT models.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Start the Demo](#start-the-demo)
|
||||
- [Development Mode](#development-mode)
|
||||
- [Build the Project](#build-the-project)
|
||||
- [Code Quality](#code-quality)
|
||||
- [Quick Commands Reference](#quick-commands-reference)
|
||||
- [How It Works](#how-it-works)
|
||||
- [API Dependencies](#api-dependencies)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Common Issues](#common-issues)
|
||||
- [License](#license)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## Features
|
||||
|
||||
- 📄 **Document Parsing**: Parse PDFs, Word docs, and other formats using LlamaParse
|
||||
- 🤖 **AI Summaries**: Generate intelligent summaries using OpenAI GPT-4
|
||||
- 🎨 **Beautiful CLI**: Styled console interface with colors and ASCII art
|
||||
- ⚡ **Fast Development**: Hot reload support with watch mode
|
||||
- 🛠️ **TypeScript**: Full TypeScript support with strict type checking
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js (version 18 or higher)
|
||||
- pnpm package manager
|
||||
- OpenAI API key
|
||||
- LlamaCloud API key
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/run-llama/llama_cloud_services
|
||||
cd lama_cloud_services/examples-ts/parse/
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. Set up your environment variables:
|
||||
|
||||
```bash
|
||||
# Add your API keys to your environment
|
||||
export OPENAI_API_KEY="your-openai-api-key"
|
||||
export LLAMA_CLOUD_API_KEY="your-llamacloud-api-key"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Start the Demo
|
||||
|
||||
```bash
|
||||
pnpm run start
|
||||
```
|
||||
|
||||
The application will display a welcome screen and prompt you to enter the path to a document you'd like to process.
|
||||
|
||||
### Development Mode
|
||||
|
||||
For development with hot reload:
|
||||
|
||||
```bash
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
### Build the Project
|
||||
|
||||
```bash
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
|
||||
Format code:
|
||||
|
||||
```bash
|
||||
pnpm run format
|
||||
```
|
||||
|
||||
Lint code:
|
||||
|
||||
```bash
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Document Input**: Enter the path to your document when prompted
|
||||
2. **Parsing**: LlamaParse processes the document and extracts structured content
|
||||
3. **AI Summary**: The extracted content is sent to OpenAI GPT-4 for summarization
|
||||
4. **Results**: View the AI-generated summary in your terminal
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Module Resolution Errors**: Ensure you're using Node.js 18+ and have all dependencies installed
|
||||
2. **API Key Issues**: Verify your OpenAI and LlamaCloud API keys are correctly set
|
||||
3. **File Path Errors**: Use absolute paths or ensure relative paths are correct from the project root
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see the [LICENSE](../../LICENSE) file for details.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Run `pnpm run format` and `pnpm run lint`
|
||||
5. Submit a pull request
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { defineConfig } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
|
||||
plugins: { js },
|
||||
extends: ["js/recommended"],
|
||||
languageOptions: { globals: globals.browser },
|
||||
},
|
||||
{ files: ["**/*.js"], languageOptions: { sourceType: "script" } },
|
||||
tseslint.configs.recommended,
|
||||
]);
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "llamaparse-demo",
|
||||
"version": "0.1.0",
|
||||
"description": "Demo for LlamaParse in TypeScript",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"There are no tests\"",
|
||||
"start": "pnpm exec tsx src/index.ts",
|
||||
"lint": "eslint ./src/",
|
||||
"format": "prettier --write ./src/",
|
||||
"build": "tsc",
|
||||
"dev": "pnpm exec tsx --watch src/index.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"ai",
|
||||
"ocr",
|
||||
"parsing",
|
||||
"intelligent-document-processing",
|
||||
"pdf",
|
||||
"llms"
|
||||
],
|
||||
"author": "LlamaIndex",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.12.4",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.32.0",
|
||||
"@types/figlet": "^1.7.0",
|
||||
"@types/node": "^24.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
||||
"@typescript-eslint/parser": "^8.38.0",
|
||||
"eslint": "^9.32.0",
|
||||
"globals": "^16.3.0",
|
||||
"jiti": "^2.5.1",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.38.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^1.3.23",
|
||||
"ai": "^4.3.19",
|
||||
"consola": "^3.4.2",
|
||||
"figlet": "^1.8.2",
|
||||
"llama-cloud-services": "link:../../ts/llama_cloud_services",
|
||||
"picocolors": "^1.1.1"
|
||||
}
|
||||
}
|
||||
Generated
+1758
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
import { LlamaParseReader } from "llama-cloud-services";
|
||||
import { logger } from "./logger";
|
||||
import pc from "picocolors";
|
||||
import { consoleInput, generateSummary, renderLogo } from "./utils";
|
||||
|
||||
export async function main(): Promise<number> {
|
||||
const reader = new LlamaParseReader({ resultType: "markdown" });
|
||||
await renderLogo();
|
||||
logger.log(
|
||||
`Welcome to ${pc.bold(
|
||||
pc.magentaBright("✨LlamaParse Demo✨"),
|
||||
)}, our demo for ${pc.bold(pc.green("LlamaParse🦙"))}, a ${pc.bold(
|
||||
pc.cyan("LlamaCloud☁️"),
|
||||
)} (https://cloud.llamaindex.ai) product!.\nType the path to the document you would like to process below👇\nIf you wish to exit, just type ${pc.bold(
|
||||
pc.gray("quit"),
|
||||
)}.\n`,
|
||||
);
|
||||
while (true) {
|
||||
const userInput = await consoleInput();
|
||||
if (userInput.toLowerCase() == "quit") {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
const documents = await reader.loadData(userInput);
|
||||
const summary = await generateSummary(documents); // Added await here
|
||||
logger.log(`${pc.bold(pc.cyan("AI-generated summary✨"))}:\n${summary}`);
|
||||
} catch (error) {
|
||||
logger.error(`Error processing file: ${error}`);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createConsola } from "consola";
|
||||
import type { ConsolaInstance } from "consola";
|
||||
|
||||
export const logger: ConsolaInstance = createConsola({
|
||||
formatOptions: {
|
||||
date: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { generateText } from "ai";
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { Document } from "llamaindex";
|
||||
import * as readline from "readline/promises";
|
||||
import figlet from "figlet";
|
||||
import pc from "picocolors";
|
||||
|
||||
export async function renderLogo(): Promise<void> {
|
||||
const logoText = figlet.textSync("LlamaParse Demo", {
|
||||
font: "ANSI Shadow",
|
||||
horizontalLayout: "default",
|
||||
verticalLayout: "default",
|
||||
width: 100,
|
||||
whitespaceBreak: true,
|
||||
});
|
||||
|
||||
// Add some styling with picocolors
|
||||
const styledLogo = pc.bold(pc.magentaBright(logoText));
|
||||
|
||||
// Add some padding/margin
|
||||
console.log("\n");
|
||||
console.log(styledLogo);
|
||||
console.log(pc.gray("─".repeat(60)));
|
||||
console.log("\n");
|
||||
}
|
||||
|
||||
export async function consoleInput(): Promise<string> {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const answer = await rl.question("Path to your file: ");
|
||||
rl.close();
|
||||
return answer;
|
||||
}
|
||||
|
||||
export async function generateSummary(documents: Document[]): Promise<string> {
|
||||
let mainText: string = "";
|
||||
|
||||
for (const document of documents) {
|
||||
mainText += `${document.text}\n\n---\n\n`;
|
||||
}
|
||||
|
||||
const { text } = await generateText({
|
||||
model: openai("gpt-4.1"),
|
||||
prompt: `</chat>\n\t<text>${mainText}</text>\n\t<instructions>Could you please generate a summary of the given text?</instructions>\n</chat>`,
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"types": ["node"],
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# LlamaCloud Services Examples - Python
|
||||
|
||||
In this folder you will find several python notebooks that contain examples regarding:
|
||||
|
||||
- [LlamaParse](./parse/)
|
||||
- [LlamaExtract](./extract/)
|
||||
- [LlamaReport](./report/)
|
||||
|
||||
Follow the instructions in each notebook to get started!
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
@@ -0,0 +1 @@
|
||||
sec_form_4_dump.json
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
@@ -0,0 +1,440 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Extract Data from Financial Reports - with Citations and Reasoning\n",
|
||||
"\n",
|
||||
"Given complex files like financial reports, contracts, invoices etc, Llama Extract allows you to make use of an LLM to extract the information relevant to you, in a structured format.\n",
|
||||
"\n",
|
||||
"In this example, we'll be using [LlamaExtract](https://docs.cloud.llamaindex.ai/llamaextract/getting_started?utm_campaign=extract&utm_medium=recipe) to extract structured data from an SEC filing (specifically, the filing by Nvidia for fiscal year 2025).\n",
|
||||
"\n",
|
||||
"On top of simple data extraction, we'll ask our extraction agent to provide citations and reasoning for each extracted field. This allows us to:\n",
|
||||
"- Confirm the accuracy of the extracted field\n",
|
||||
"- Understand the reasoning behind why the LLM extracted a given piece of information\n",
|
||||
"- This last point allows us an opportunity to adjust the system prompt or field descriptions and improve on results where needed.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"The example we go through below is also replicable within Llama Cloud as well, where you will also be able to pick between a number of pre-defined schemas, instead of building your own."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Connect to Llama Cloud\n",
|
||||
"\n",
|
||||
"To get started, make sure you provide your [Llama Cloud](https://cloud.llamaindex.ai?utm_campaign=extract&utm_medium=recipe) API key."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Enter your Llama Cloud API Key: ··········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"from getpass import getpass\n",
|
||||
"\n",
|
||||
"if \"LLAMA_CLOUD_API_KEY\" not in os.environ:\n",
|
||||
" os.environ[\"LLAMA_CLOUD_API_KEY\"] = getpass(\"Enter your Llama Cloud API Key: \")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Extract Data with Llama Extract Agent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"No project_id provided, fetching default project.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaExtract\n",
|
||||
"\n",
|
||||
"# Optionally, provide your project id, if not, it will use the 'Default' project\n",
|
||||
"llama_extract = LlamaExtract()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Provide Your Custom Schema\n",
|
||||
"\n",
|
||||
"When using LlamaExtract via the API, you provide your own schema that describes what you want extracted from files and data provided to your agent. Here, we are essentially building an SEC filings extraction agent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pydantic import BaseModel, Field\n",
|
||||
"from enum import Enum\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class FilingType(str, Enum):\n",
|
||||
" ten_k = \"10 K\"\n",
|
||||
" ten_q = \"10-Q\"\n",
|
||||
" ten_ka = \"10-K/A\"\n",
|
||||
" ten_qa = \"10-Q/A\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class FinancialReport(BaseModel):\n",
|
||||
" company_name: str = Field(description=\"The name of the company\")\n",
|
||||
" description: str = Field(\n",
|
||||
" description=\"Short description of the filing and what it contains\"\n",
|
||||
" )\n",
|
||||
" filing_type: FilingType = Field(description=\"Type of SEC filing\")\n",
|
||||
" filing_date: str = Field(description=\"Date when filing was submitted to SEC\")\n",
|
||||
" fiscal_year: int = Field(description=\"Fiscal year\")\n",
|
||||
" unit: str = Field(\n",
|
||||
" description=\"Unit of financial figures (thousands, millions, etc.)\"\n",
|
||||
" )\n",
|
||||
" revenue: int = Field(description=\"Total revenue for period\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Set Up Citations and Reasoning\n",
|
||||
"\n",
|
||||
"Optionally, we can set the `ExtractConfig` to extract citations for each field the agent extracts. These cications will cite the specific pages and sections of the file from which a given field was extractedd.\n",
|
||||
"\n",
|
||||
"By setting `use_reasoning` to True, we als ask the agent to do an additional reasoning step, explaining why a given field was extracted."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_cloud.types import ExtractConfig, ExtractMode\n",
|
||||
"\n",
|
||||
"config = ExtractConfig(\n",
|
||||
" use_reasoning=True, cite_sources=True, extraction_mode=ExtractMode.MULTIMODAL\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/usr/local/lib/python3.11/dist-packages/llama_cloud_services/extract/extract.py:127: ExperimentalWarning: `use_reasoning` is an experimental feature. Results will be available in the `extraction_metadata` field for the extraction run.\n",
|
||||
" warnings.warn(\n",
|
||||
"/usr/local/lib/python3.11/dist-packages/llama_cloud_services/extract/extract.py:133: ExperimentalWarning: `cite_sources` is an experimental feature. This may greatly increase the size of the response, and slow down the extraction. Results will be available in the `extraction_metadata` field for the extraction run.\n",
|
||||
" warnings.warn(\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"agent = llama_extract.create_agent(\n",
|
||||
" name=\"filing-parser\", data_schema=FinancialReport, config=config\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Demo Time - Download a PDF and Extract Data with Citations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"PDF downloaded successfully.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import requests\n",
|
||||
"\n",
|
||||
"url = \"https://raw.githubusercontent.com/run-llama/llama_cloud_services/refs/heads/main/examples/extract/data/sec_filings/nvda_10k.pdf\"\n",
|
||||
"\n",
|
||||
"response = requests.get(url)\n",
|
||||
"\n",
|
||||
"if response.status_code == 200:\n",
|
||||
" with open(\"/content/nvda_10k.pdf\", \"wb\") as f:\n",
|
||||
" f.write(response.content)\n",
|
||||
" print(\"PDF downloaded successfully.\")\n",
|
||||
"else:\n",
|
||||
" print(f\"Failed to download. Status code: {response.status_code}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Uploading files: 100%|██████████| 1/1 [00:00<00:00, 1.83it/s]\n",
|
||||
"Creating extraction jobs: 100%|██████████| 1/1 [00:00<00:00, 4.38it/s]\n",
|
||||
"Extracting files: 100%|██████████| 1/1 [02:03<00:00, 123.40s/it]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"filing_info = agent.extract(\"/content/nvda_10k.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'company_name': 'NVIDIA Corporation',\n",
|
||||
" 'description': \"The filing provides a detailed overview of NVIDIA's business as a full-stack computing infrastructure company, discusses various technologies including digital avatars and autonomous vehicles, outlines numerous risk factors affecting operations such as supply chain issues and geopolitical tensions, and describes employee stock purchase plans and related compliance requirements.\",\n",
|
||||
" 'filing_type': '10 K',\n",
|
||||
" 'filing_date': 'February 26, 2025',\n",
|
||||
" 'fiscal_year': 2025,\n",
|
||||
" 'unit': 'millions',\n",
|
||||
" 'revenue': 130497}"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"filing_info.data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Inspect Citations and Reasoning"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'field_metadata': {'company_name': {'reasoning': 'VERBATIM EXTRACTION',\n",
|
||||
" 'citation': [{'page': 1, 'matching_text': 'NVIDIA CORPORATION'},\n",
|
||||
" {'page': 2, 'matching_text': 'NVIDIA Corporation'},\n",
|
||||
" {'page': 3,\n",
|
||||
" 'matching_text': 'All references to \"NVIDIA,\" \"we,\" \"us,\" \"our,\" or the \"Company\" mean NVIDIA Corporation and its subsidiaries.'},\n",
|
||||
" {'page': 35,\n",
|
||||
" 'matching_text': 'Comparison of 5 Year Cumulative Total Return* Among NVIDIA Corporation'},\n",
|
||||
" {'page': 49,\n",
|
||||
" 'matching_text': 'To the Board of Directors and Shareholders of NVIDIA Corporation'},\n",
|
||||
" {'page': 90, 'matching_text': 'NVIDIA Corporation'},\n",
|
||||
" {'page': 119,\n",
|
||||
" 'matching_text': '*\"Company\"* means NVIDIA Corporation, a Delaware corporation.'},\n",
|
||||
" {'page': 126,\n",
|
||||
" 'matching_text': 'Annual Report on Form 10-K of NVIDIA Corporation'}]},\n",
|
||||
" 'filing_type': {'reasoning': \"VERBATIM EXTRACTION from multiple sources confirming the filing type as '10 K'.\",\n",
|
||||
" 'citation': [{'page': 1, 'matching_text': 'FORM 10-K'},\n",
|
||||
" {'page': 2, 'matching_text': 'Item 16. | Form 10-K Summary'},\n",
|
||||
" {'page': 3,\n",
|
||||
" 'matching_text': 'This Annual Report on Form 10-K contains forward-looking statements...'},\n",
|
||||
" {'page': 13, 'matching_text': 'this Annual Report on Form 10-K'},\n",
|
||||
" {'page': 15, 'matching_text': 'this Annual Report on Form 10-K'},\n",
|
||||
" {'page': 32,\n",
|
||||
" 'matching_text': 'Annual Report on Form 10-K, which information is hereby incorporated by reference.'},\n",
|
||||
" {'page': 36, 'matching_text': 'this Annual Report on Form 10-K'},\n",
|
||||
" {'page': 43,\n",
|
||||
" 'matching_text': 'Annual Report on Form 10-K for additional information'},\n",
|
||||
" {'page': 45, 'matching_text': 'Annual Report on Form 10-K'},\n",
|
||||
" {'page': 46, 'matching_text': 'this Annual Report on Form 10-K'},\n",
|
||||
" {'page': 62, 'matching_text': 'Annual Report on Form 10-K'},\n",
|
||||
" {'page': 83,\n",
|
||||
" 'matching_text': 'Restated Certificate of Incorporation | 10-K'},\n",
|
||||
" {'page': 84, 'matching_text': 'Item 16. Form 10-K Summary'},\n",
|
||||
" {'page': 126, 'matching_text': 'which appears in this Form 10-K'},\n",
|
||||
" {'page': 127, 'matching_text': 'Annual Report on Form 10-K'},\n",
|
||||
" {'page': 128, 'matching_text': 'Annual Report on Form 10-K'},\n",
|
||||
" {'page': 129, 'matching_text': \"The Company's Annual Report on Form 10-K\"},\n",
|
||||
" {'page': 130,\n",
|
||||
" 'matching_text': \"The Company's Annual Report on Form 10-K for the year ended January 26, 2025\"}]},\n",
|
||||
" 'fiscal_year': {'reasoning': 'The fiscal year ended January 26, 2025, indicates the fiscal year is 2025. Additionally, multiple references throughout the text confirm the fiscal year 2025 in various contexts.',\n",
|
||||
" 'citation': [{'page': 1,\n",
|
||||
" 'matching_text': 'For the fiscal year ended January 26, 2025'},\n",
|
||||
" {'page': 6,\n",
|
||||
" 'matching_text': 'In fiscal year 2025, we launched the NVIDIA Blackwell architecture'},\n",
|
||||
" {'page': 12, 'matching_text': 'fiscal year 2025'},\n",
|
||||
" {'page': 17,\n",
|
||||
" 'matching_text': 'our gross margins in the second quarter of fiscal year 2025 were negatively impacted'},\n",
|
||||
" {'page': 20,\n",
|
||||
" 'matching_text': 'we generated 53% of our revenue in fiscal year 2025 from sales outside the United States.'},\n",
|
||||
" {'page': 23,\n",
|
||||
" 'matching_text': 'For fiscal year 2025, an indirect customer which primarily purchases our products through system integrators...'},\n",
|
||||
" {'page': 33,\n",
|
||||
" 'matching_text': 'In fiscal year 2025, we repurchased 310 million shares of our common stock for $34.0 billion.'},\n",
|
||||
" {'page': 37,\n",
|
||||
" 'matching_text': 'Our Data Center revenue in China grew in fiscal year 2025.'},\n",
|
||||
" {'page': 44,\n",
|
||||
" 'matching_text': 'Cash provided by operating activities increased in fiscal year 2025 compared to fiscal year 2024'},\n",
|
||||
" {'page': 57,\n",
|
||||
" 'matching_text': 'Fiscal years 2025, 2024 and 2023 were all 52-week years.'},\n",
|
||||
" {'page': 65,\n",
|
||||
" 'matching_text': 'Beginning in the second quarter of fiscal year 2025'},\n",
|
||||
" {'page': 69, 'matching_text': 'In the fourth quarter of fiscal year 2025'},\n",
|
||||
" {'page': 78,\n",
|
||||
" 'matching_text': 'Depreciation and amortization expense attributable to our Compute and Networking segment for fiscal years 2025'},\n",
|
||||
" {'page': 129, 'matching_text': 'for the year ended January 26, 2025'}]},\n",
|
||||
" 'description': {'reasoning': 'The extracted data combines multiple descriptions from the source text, ensuring no duplication while maintaining the order and context of the information. Each section of the filing is summarized to reflect the key points without losing the essence of the original text.',\n",
|
||||
" 'citation': [{'page': 4,\n",
|
||||
" 'matching_text': 'NVIDIA is now a full-stack computing infrastructure company with data-center-scale offerings that are reshaping industry.'},\n",
|
||||
" {'page': 8,\n",
|
||||
" 'matching_text': 'a suite of technologies that help developers bring digital avatars to life with generative Al...autonomous vehicles, or AV, and electric vehicles, or EV, is revolutionizing the transportation industry...Our worldwide sales and marketing strategy is key to achieving our objective of providing markets with our high-performance and efficient computing platforms and software.'},\n",
|
||||
" {'page': 14, 'matching_text': 'Risk Factors Summary'},\n",
|
||||
" {'page': 16,\n",
|
||||
" 'matching_text': 'Risks Related to Demand, Supply, and Manufacturing\\n\\nLong manufacturing lead times and uncertain supply and component availability...'},\n",
|
||||
" {'page': 18,\n",
|
||||
" 'matching_text': 'cryptocurrency mining, on demand for our products. Volatility in the cryptocurrency market, including new compute technologies...'},\n",
|
||||
" {'page': 21,\n",
|
||||
" 'matching_text': 'supply-chain attacks or other business disruptions. We cannot guarantee that third parties and infrastructure in our supply chain...'},\n",
|
||||
" {'page': 22,\n",
|
||||
" 'matching_text': 'We are monitoring the impact of the geopolitical conflict in and around Israel on our operations... Climate change may have a long-term impact on our business.'},\n",
|
||||
" {'page': 25,\n",
|
||||
" 'matching_text': 'We are subject to complex laws, rules, regulations, and political and other actions, including restrictions on the export of our products, which may adversely impact our business.'},\n",
|
||||
" {'page': 28,\n",
|
||||
" 'matching_text': 'Our competitive position has been harmed by the existing export controls, and our competitive position and future results may be further harmed'},\n",
|
||||
" {'page': 29,\n",
|
||||
" 'matching_text': 'restrictions imposed by the Chinese government on the duration of gaming activities and access to games may adversely affect our Gaming revenue'},\n",
|
||||
" {'page': 29,\n",
|
||||
" 'matching_text': 'our business depends on our ability to receive consistent and reliable supply from our overseas partners, especially in Taiwan and South Korea'},\n",
|
||||
" {'page': 29,\n",
|
||||
" 'matching_text': 'Increased scrutiny from shareholders, regulators and others regarding our corporate sustainability practices could result in additional costs'},\n",
|
||||
" {'page': 29,\n",
|
||||
" 'matching_text': 'Concerns relating to the responsible use of new and evolving technologies, such as Al, in our products and services may result in reputational or financial harm'},\n",
|
||||
" {'page': 31,\n",
|
||||
" 'matching_text': 'Data protection laws around the world are quickly changing and may be interpreted and applied in an increasingly stringent fashion...'}]},\n",
|
||||
" 'filing_date': {'reasoning': 'The filing date is consistently mentioned as February 26, 2025 across multiple entries, making it the most reliable date for the filing.',\n",
|
||||
" 'citation': [{'page': 51, 'matching_text': 'February 26, 2025'},\n",
|
||||
" {'page': 86, 'matching_text': 'on February 26, 2025.'},\n",
|
||||
" {'page': 87, 'matching_text': 'February 26, 2025'},\n",
|
||||
" {'page': 126, 'matching_text': 'our report dated February 26, 2025'},\n",
|
||||
" {'page': 127, 'matching_text': 'Date: February 26, 2025'},\n",
|
||||
" {'page': 128, 'matching_text': 'Date: February 26, 2025'},\n",
|
||||
" {'page': 129, 'matching_text': 'Date: February 26, 2025'},\n",
|
||||
" {'page': 130, 'matching_text': 'Date: February 26, 2025'}]},\n",
|
||||
" 'unit': {'reasoning': \"The unit of financial figures is explicitly mentioned multiple times in the text as 'millions', including in table headers and notes. This is confirmed by various citations from pages 38, 42, 43, 52, 53, 54, 56, 65, 71, 72, 73, 75, 77, 79, 80, and 82.\",\n",
|
||||
" 'citation': [{'page': 38,\n",
|
||||
" 'matching_text': '($ in millions, except per share data)'},\n",
|
||||
" {'page': 42, 'matching_text': '($ in millions)'},\n",
|
||||
" {'page': 43, 'matching_text': '($ in millions)'},\n",
|
||||
" {'page': 52, 'matching_text': '(In millions, except per share data)'},\n",
|
||||
" {'page': 53,\n",
|
||||
" 'matching_text': 'Consolidated Statements of Comprehensive Income (In millions)'},\n",
|
||||
" {'page': 54,\n",
|
||||
" 'matching_text': 'Consolidated Balance Sheets (In millions, except par value)'},\n",
|
||||
" {'page': 55, 'matching_text': '(In millions, except per share data)'},\n",
|
||||
" {'page': 56,\n",
|
||||
" 'matching_text': 'Consolidated Statements of Cash Flows (In millions)'},\n",
|
||||
" {'page': 65,\n",
|
||||
" 'matching_text': 'Year Ended<br/>Jan 26, 2025<br/>(In millions, except per share data)'},\n",
|
||||
" {'page': 71, 'matching_text': '(In millions) | (In millions)'},\n",
|
||||
" {'page': 72, 'matching_text': '(In millions)'}]},\n",
|
||||
" 'revenue': {'reasoning': 'The total revenue for fiscal year 2025 is extracted from multiple sources within the text, all confirming the same figure of $130,497 million. The revenue recognized for fiscal year 2025 is also noted as $4,607 million, which is a separate figure. However, the primary focus is on the total revenue figure, which is consistently cited.',\n",
|
||||
" 'citation': [{'page': 38,\n",
|
||||
" 'matching_text': 'Revenue for fiscal year 2025 was $130.5 billion'},\n",
|
||||
" {'page': 41,\n",
|
||||
" 'matching_text': 'Total | $ 130,497 | $ | 60,922'},\n",
|
||||
" {'page': 52, 'matching_text': 'Revenue | $ 130,497'},\n",
|
||||
" {'page': 78,\n",
|
||||
" 'matching_text': 'Revenue | $ 116,193 | $ 14,304 | $ - | $ 130,497'},\n",
|
||||
" {'page': 79, 'matching_text': 'Total revenue | $ 130,497'},\n",
|
||||
" {'page': 80, 'matching_text': 'Total revenue | $ 130,497'}]}},\n",
|
||||
" 'usage': {'num_pages_extracted': 130,\n",
|
||||
" 'num_document_tokens': 105932,\n",
|
||||
" 'num_output_tokens': 31306}}"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"filing_info.extraction_metadata"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## What's Next?\n",
|
||||
"\n",
|
||||
"In this example, we built an Extraction Agent that is capable of citing it's sources from the document it's extracting data from, and reasoning about its reponse. To further customize and improve on the results, you can also try to customize the `system_prompt` in the `ExtractConfig`.\n",
|
||||
"\n",
|
||||
"#### Learn More\n",
|
||||
"\n",
|
||||
"- [LlamaExtract Documentation](https://docs.cloud.llamaindex.ai/llamaextract/getting_started)\n",
|
||||
"- [Example Notebooks](https://github.com/run-llama/llama_cloud_services/tree/main/examples/extract)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"provenance": []
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+417
-558
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,295 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Using llama-parse with AstraDB"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this notebook, we show a basic RAG-style example that uses `llama-parse` to parse a PDF document, store the corresponding document into a vector store (`AstraDB`) and finally, perform some basic queries against that store. The notebook is modeled after the quick start notebooks and hence is meant as a way of getting started with `llama-parse`, backed by a vector database."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Requirements"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# First, install the required dependencies\n",
|
||||
"%pip install --quiet llama-index llama-parse llama-index-vector-stores-astra-db llama-index-llms-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Configuration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import openai\n",
|
||||
"\n",
|
||||
"from getpass import getpass\n",
|
||||
"\n",
|
||||
"# Get all required API keys and parameters\n",
|
||||
"llama_cloud_api_key = getpass(\"Enter your Llama Index Cloud API Key: \")\n",
|
||||
"api_endpoint = input(\"Enter your Astra DB API Endpoint: \")\n",
|
||||
"token = getpass(\"Enter your Astra DB Token: \")\n",
|
||||
"namespace = (\n",
|
||||
" input(\"Enter your Astra DB namespace (optional, must exist on Astra): \") or None\n",
|
||||
")\n",
|
||||
"openai_api_key = getpass(\"Enter your OpenAI API Key: \")\n",
|
||||
"\n",
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = llama_cloud_api_key\n",
|
||||
"openai.api_key = openai_api_key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the sync code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Using llama-parse to parse a PDF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Download complete.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Grab a PDF from Arxiv for indexing\n",
|
||||
"import requests\n",
|
||||
"\n",
|
||||
"# The URL of the file you want to download\n",
|
||||
"url = \"https://arxiv.org/pdf/1706.03762.pdf\"\n",
|
||||
"# The local path where you want to save the file\n",
|
||||
"file_path = \"./attention.pdf\"\n",
|
||||
"\n",
|
||||
"# Perform the HTTP request\n",
|
||||
"response = requests.get(url)\n",
|
||||
"\n",
|
||||
"# Check if the request was successful\n",
|
||||
"if response.status_code == 200:\n",
|
||||
" # Open the file in binary write mode and save the content\n",
|
||||
" with open(file_path, \"wb\") as file:\n",
|
||||
" file.write(response.content)\n",
|
||||
" print(\"Download complete.\")\n",
|
||||
"else:\n",
|
||||
" print(\"Error downloading the file.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id ce3909a7-54cf-438b-849a-fe9a903b0c71\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"documents = LlamaParse(result_type=\"text\").load_data(file_path)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'rmer - model architecture.\\nThe Transformer follows this overall architecture using stacked self-attention and point-wise, fully\\nconnected layers for both the encoder and decoder, shown in the left and right halves of Figure 1,\\nrespectively.\\n3.1 Encoder and Decoder Stacks\\nEncoder: The encoder is composed of a stack of N = 6 identical layers. Each layer has two\\nsub-layers. The first is a multi-head self-attention mechanism, and the second is a simple, position-\\nwise fully connected feed-forward network. We employ a residual connection [11] around each of\\nthe two sub-layers, followed by layer normalization [1]. That is, the output of each sub-layer is\\nLayerNorm(x + Sublayer(x)), where Sublayer(x) is the function implemented by the sub-layer\\nitself. To facilitate these residual connections, all sub-layers in the model, as well as the embedding\\nlayers, produce outputs of dimension dmodel = 512.\\nDecoder: The decoder is also composed of a stack of N = 6 identical layers. In addition '"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Take a quick look at some of the parsed text from the document:\n",
|
||||
"documents[0].get_content()[10000:11000]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Storing into Astra DB"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.vector_stores.astra_db import AstraDBVectorStore\n",
|
||||
"\n",
|
||||
"astra_db_store = AstraDBVectorStore(\n",
|
||||
" token=token,\n",
|
||||
" api_endpoint=api_endpoint,\n",
|
||||
" namespace=namespace,\n",
|
||||
" collection_name=\"astra_v_table_llamaparse\",\n",
|
||||
" embedding_dimension=1536,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.node_parser import SimpleNodeParser\n",
|
||||
"\n",
|
||||
"node_parser = SimpleNodeParser()\n",
|
||||
"\n",
|
||||
"nodes = node_parser.get_nodes_from_documents(documents)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.embeddings.openai import OpenAIEmbedding\n",
|
||||
"from llama_index.core import VectorStoreIndex, StorageContext\n",
|
||||
"\n",
|
||||
"storage_context = StorageContext.from_defaults(vector_store=astra_db_store)\n",
|
||||
"\n",
|
||||
"index = VectorStoreIndex(\n",
|
||||
" nodes=nodes,\n",
|
||||
" storage_context=storage_context,\n",
|
||||
" embed_model=OpenAIEmbedding(api_key=openai_api_key),\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Simple RAG Example"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"query_engine = index.as_query_engine(similarity_top_k=15)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"***********New LlamaParse+ Basic Query Engine***********\n",
|
||||
"Multi-Head Attention is also known as multi-headed self-attention.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"query = \"What is Multi-Head Attention also known as?\"\n",
|
||||
"\n",
|
||||
"response_1 = query_engine.query(query)\n",
|
||||
"print(\"\\n***********New LlamaParse+ Basic Query Engine***********\")\n",
|
||||
"print(response_1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'We used beam search as described in the previous section, but no\\ncheckpoint averaging. We present these results in Table 3.\\nIn Table 3 rows (A), we vary the number of attention heads and the attention key and value dimensions,\\nkeeping the amount of computation constant, as described in Section 3.2.2. While single-head\\nattention is 0.9 BLEU worse than the best setting, quality also drops off with too many heads.\\nIn Table 3 rows (B), we observe that reducing the attention key size dk hurts model quality. This\\nsuggests that determining compatibility is not easy and that a more sophisticated compatibility\\nfunction than dot product may be beneficial. We further observe in rows (C) and (D) that, as expected,\\nbigger models are better, and dropout is very helpful in avoiding over-fitting. In row (E) we replace our\\nsinusoidal positional encoding with learned positional embeddings [9], and observe nearly identical\\nresults to the base model.\\n6.3 English Constituency Parsing\\nTo evaluate if the Transformer can generalize to other tasks we performed experiments on English\\nconstituency parsing. This task presents specific challenges: the output is subject to strong structural\\nconstraints and is significantly longer than the input. Furthermore, RNN sequence-to-sequence\\nmodels have not been able to attain state-of-the-art results in small-data regimes [37].\\nWe trained a 4-layer transformer with dmodel = 1024 on the Wall Street Journal (WSJ) portion of the\\nPenn Treebank [25], about 40K training sentences. We also trained it in a semi-supervised setting,\\nusing the larger high-confidence and BerkleyParser corpora from with approximately 17M sentences\\n[37]. We used a vocabulary of 16K tokens for the WSJ only setting and a vocabulary of 32K tokens\\nfor the semi-supervised setting.\\nWe performed only a small number of experiments to select the dropout, both attention and residual\\n(section 5.4), learning rates and beam size on the Section 22 development set, all other parameters\\nremained unchanged from the English-to-German base translation model. During inference, we\\n 9\\n---\\nTable 4: The Transformer generalizes well to English constituency parsing (Results are on Section 23\\nof WSJ)\\n Parser Training WSJ 23 F1\\n Vinyals & Kaiser el al. (2014) [37] WSJ only, discriminative 88.3\\n Petrov et al. (2006) [29] WSJ only, discriminative 90.4\\n Zhu et al. (2013) [40] WSJ only, discriminative 90.4\\n Dyer et al. (2016) [8] WSJ only, discriminative 91.7\\n Transformer (4 layers) WSJ only, discriminative 91.3\\n Zhu et al. (2013) [40] semi-supervised 91.3\\n Huang & Harper (2009) [14] semi-supervised 91.3\\n McClosky et al. (2006) [26] semi-supervised 92.1\\n Vinyals & Kaiser el al. (2014) [37] semi-supervised 92.1\\n Transformer (4 layers) semi-supervised 92.7\\n Luong et al. (2015) [23] multi-task 93.0\\n Dyer et al. (2016) [8] generative 93.3\\nincreased the maximum output length to input length + 300. We used a beam size of 21 and α = 0.3\\nfor both WSJ only and the semi-supervised setting.\\nOur results in Table 4 show that despite the lack of task-specific tuning our model performs sur-\\nprisingly well, yielding better results than all previously reported models with the exception of the\\nRecurrent Neural Network Grammar [8].\\nIn contrast to RNN sequence-to-sequence models [37], the Transformer outperforms the Berkeley-\\nParser [29] even when training only on the WSJ training set of 40K sentences.\\n7 Conclusion\\nIn this work, we presented the Transformer, the first sequence transduction model based entirely on\\nattention, replacing the recurrent layers most commonly used in encoder-decoder architectures with\\nmulti-headed self-attention.\\nFor translation tasks, the Transformer can be trained significantly faster than architectures based\\non recurrent or convolutional layers.'"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Take a look at one of the source nodes from the response\n",
|
||||
"response_1.source_nodes[0].get_content()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
@@ -13,32 +13,14 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install llama-index llama-parse"
|
||||
"%pip install llama-index llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--2024-02-02 11:10:10-- https://arxiv.org/pdf/1706.03762.pdf\n",
|
||||
"Resolving arxiv.org (arxiv.org)... 151.101.131.42, 151.101.3.42, 151.101.67.42, ...\n",
|
||||
"Connecting to arxiv.org (arxiv.org)|151.101.131.42|:443... connected.\n",
|
||||
"HTTP request sent, awaiting response... 200 OK\n",
|
||||
"Length: 2215244 (2.1M) [application/pdf]\n",
|
||||
"Saving to: ‘./attention.pdf’\n",
|
||||
"\n",
|
||||
"./attention.pdf 100%[===================>] 2.11M --.-KB/s in 0.08s \n",
|
||||
"\n",
|
||||
"2024-02-02 11:10:10 (25.9 MB/s) - ‘./attention.pdf’ saved [2215244/2215244]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget \"https://arxiv.org/pdf/1706.03762.pdf\" -O \"./attention.pdf\""
|
||||
]
|
||||
@@ -49,11 +31,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the sync code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-...\""
|
||||
@@ -68,14 +45,14 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id dd0b8e31-0c09-4497-b78a-cc1c92f1d6cf\n"
|
||||
"Started parsing the file under job_id 79ae653c-4598-4bd0-ba6e-b3dab7eab57e\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"documents = LlamaParse(result_type=\"text\").load_data(\"./attention.pdf\")"
|
||||
"result = await LlamaParse().aparse(\"./attention.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -87,23 +64,62 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ad\n",
|
||||
"1 Introduction\n",
|
||||
"Recurrent neural networks, long short-term memory [13] and gated recurrent [7] neural networks\n",
|
||||
"in particular, have been firmly established as state of the art approaches in sequence modeling and\n",
|
||||
"transduction problems such as language modeling and machine translation [35, 2, 5]. Numerous\n",
|
||||
"efforts have since continued to push the boundaries of recurrent language models and encoder-decoder\n",
|
||||
"architectures [38, 24, 15].\n",
|
||||
"Recurrent models typically factor computation along the symbol positions of the input and output\n",
|
||||
"sequences. Aligning the positions to steps in computation time, they generate a sequence of hidden\n",
|
||||
"states ht, as a function of the previous hidden state ht−1 and the input for position t. This inherently\n",
|
||||
"sequential nature precludes parallelization within training examples, which becomes critical at longer\n",
|
||||
"sequence lengths, as memory constraints limit batching across examples. Recent work has achieved\n",
|
||||
"significant improvements in computational efficiency through factorization tricks [21] and conditional\n",
|
||||
"computation [32], while also improving model performance in case of the latter. The fundamental\n",
|
||||
"constraint of sequential computation, however, remains.\n",
|
||||
"Attention mechanisms have become an integral part of compelling sequence modeling and transduc-\n",
|
||||
"tion models in various tasks, allowing modeling of dependencies without regard to their distance in\n",
|
||||
"the input or output sequences [2, 19]. In all but a few cases [27], however, such attention mechanisms\n",
|
||||
"are used in conjunction with a recurrent network.\n",
|
||||
"In this work we propose the Transformer, a model architecture eschewing recurrence and instead\n",
|
||||
"relying entirely on an attention mechanism to draw global dependencies between input and output.\n",
|
||||
"The Transformer allows for significantly more parallelization and can reach a new state of the art in\n",
|
||||
"translation quality after being trained for as little as twelve hours on eight P100 GPUs.\n",
|
||||
"2 Background\n",
|
||||
"2 Background\n",
|
||||
"The goal of reducing sequential computation also forms the foundation of the Extended Neural GPU\n",
|
||||
"[16], ByteNet [18] and ConvS2S [9], all of which use convolutional neural networks as basic building\n",
|
||||
"block, computing hidden representations in parallel for all input and output positions. In these models,\n",
|
||||
"the number of operations required to relate signals from two arbitrary input or output positions grows\n",
|
||||
"in the distance between positions, linearly for ConvS2S and logarithmically for ByteNet. This makes\n",
|
||||
"it more difficult to learn dependencies between distant positions [12]. In the Transformer this is\n",
|
||||
"reduced to a constant number of operations, albeit at the cost of reduced effective res\n"
|
||||
"reduced to a constant number of operations, albeit at the cost of reduced effective resolution due\n",
|
||||
"to averaging attention-weighted positions, an effect we counteract with Multi-Head Attention as\n",
|
||||
"described in section 3.2.\n",
|
||||
"Self-attention, sometimes called intra-attention is an attention mechanism relating different positions\n",
|
||||
"of a single sequence in order to compute a representation of the sequence. Self-attention has been\n",
|
||||
"used successfully in a variety of tasks including reading comprehension, abstractive summarization,\n",
|
||||
"textual entailment and learning task-independent sentence representations [4, 27, 28, 22].\n",
|
||||
"End-to-end memory networks are based on a recurrent attention mechanism instead of sequence-\n",
|
||||
"aligned recurrence and have been shown to perform well on simple-language question answering and\n",
|
||||
"language modeling tasks [34].\n",
|
||||
"To the best of our knowledge, however, the Transformer is the first transduction model relying\n",
|
||||
"entirely on self-attention to compute representations of its input and output without using sequence-\n",
|
||||
"aligned RNNs or convolution. In the following sections, we will describe the Transformer, motivate\n",
|
||||
"self-attention and discuss its advantages over models such as [17, 18] and [9].\n",
|
||||
"3 Model Architecture\n",
|
||||
"Most competitive neural sequence transduction models have an encoder-decoder structure [5, 2, 35].\n",
|
||||
"Here, the encoder maps an input sequence of symbol representations (x1, ..., xn) to a sequence\n",
|
||||
"of continuous representations z = (z1, ..., zn). Given z, the decoder then generates an output\n",
|
||||
"sequence (y1, ..., ym) of symbols one element at a time. At each step the model is auto-regressive\n",
|
||||
"[10], consuming the previously generated symbols as additional input when generating the next.\n",
|
||||
" 2\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(documents[0].text[6000:7000])"
|
||||
"documents = result.get_text_documents(split_by_page=True)\n",
|
||||
"print(documents[1].text)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -115,48 +131,45 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id d4531453-1bbb-48c4-8324-ae9fea9f2fa2\n"
|
||||
"arXiv:1706.03762v7 [cs.CL] 2 Aug 2023\n",
|
||||
"\n",
|
||||
"Provided proper attribution is provided, Google hereby grants permission to reproduce the tables and figures in this paper solely for use in journalistic or scholarly works.\n",
|
||||
"\n",
|
||||
"# Attention Is All You Need\n",
|
||||
"\n",
|
||||
"Ashish Vaswani∗ Noam Shazeer∗ Niki Parmar∗ Jakob Uszkoreit∗\n",
|
||||
"\n",
|
||||
"Google Brain Google Brain Google Research Google Research\n",
|
||||
"\n",
|
||||
"avaswani@google.com noam@google.com nikip@google.com usz@google.com\n",
|
||||
"\n",
|
||||
"Llion Jones∗ Aidan N. Gomez∗ † Łukasz Kaiser∗\n",
|
||||
"\n",
|
||||
"Google Research University of Toronto Google Brain\n",
|
||||
"\n",
|
||||
"llion@google.com aidan@cs.toronto.edu lukaszkaiser@google.com\n",
|
||||
"\n",
|
||||
"Illia Polosukhin∗ ‡\n",
|
||||
"\n",
|
||||
"illia.polosukhin@gmail.com\n",
|
||||
"\n",
|
||||
"# Abstract\n",
|
||||
"\n",
|
||||
"The dominant sequence transduction models are based on complex recurrent or convolutional neural networks that include an encoder and a decoder. The best performing models also connect the encoder and decoder through an attention mechanism. We propose a new simple network architecture, the Transformer, based solely on attention mechanisms, dispensing with recurrence and convolutions entirely. Experiments on two machine translation tasks show these models to be superior in quality while being more parallelizable and requiring significantly less time to train. Our model achieves 28.4 BLEU on the WMT 2014 English-to-German translation task, improving over the existing best results, including ensembles, by over 2 BLEU. On the WMT 2014 English-to-French translation task, our model establishes a new single-model state-of-the-art BLEU score of 41.8 after training for 3.5 days on eight GPUs, a small fraction of the training costs of the best models from the literature. We show that the Transformer generalizes well to other tasks by applying it successfully to English constituency parsing both with large and limited training data.\n",
|
||||
"\n",
|
||||
"∗Equal contribution. Listing order is random. Jakob proposed replacing RNNs with self-attention and started the effort to evaluate this idea. Ashish, with Illia, designed and implemented the first Transformer models and has been crucially involved in every aspect of this work. Noam proposed scaled dot-product attention, multi-head attention and the parameter-free position representation and became the other person involved in nearly every detail. Niki designed, implemented, tuned and evaluated countless model variants in our original codebase and tensor2tensor. Llion also experimented with novel model variants, was responsible for our initial codebase, and efficient inference and visualizations. Lukasz and Aidan spent countless long days designing various parts of and implementing tensor2tensor, replacing our earlier codebase, greatly improving results and massively accelerating our research.\n",
|
||||
"\n",
|
||||
"†Work performed while at Google Brain.\n",
|
||||
"\n",
|
||||
"‡Work performed while at Google Research.\n",
|
||||
"\n",
|
||||
"31st Conference on Neural Information Processing Systems (NIPS 2017), Long Beach, CA, USA.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"documents = LlamaParse(result_type=\"markdown\").load_data(\"./attention.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ction describes the training regime for our models.\n",
|
||||
"\n",
|
||||
"##### Training Data and Batching\n",
|
||||
"\n",
|
||||
"We trained on the standard WMT 2014 English-German dataset consisting of about 4.5 million\n",
|
||||
"sentence pairs. Sentences were encoded using byte-pair encoding [3], which has a shared source-\n",
|
||||
"target vocabulary of about 37000 tokens. For English-French, we used the significantly larger WMT\n",
|
||||
"2014 English-French dataset consisting of 36M sentences and split tokens into a 32000 word-piece\n",
|
||||
"vocabulary [38]. Sentence pairs were batched together by approximate sequence length. Each training\n",
|
||||
"batch contained a set of sentence pairs containing approximately 25000 source tokens and 25000\n",
|
||||
"target tokens.\n",
|
||||
"\n",
|
||||
"##### Hardware and Schedule\n",
|
||||
"\n",
|
||||
"We trained our models on one machine with 8 NVIDIA P100 GPUs. For our base models using\n",
|
||||
"the hyperparameters described throughout the paper, each training step took about 0.4 seconds. We\n",
|
||||
"trained the base models for a total of 100,000 steps or 12 hours. For our big models,(described on the\n",
|
||||
"bo...\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(documents[0].text[20000:21000] + \"...\")"
|
||||
"documents = result.get_markdown_documents(split_by_page=True)\n",
|
||||
"print(documents[0].text)"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -31,11 +31,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install llama-index\n",
|
||||
"!pip install llama-index-core\n",
|
||||
"!pip install llama-index-llms-anthropic llama-index-multi-modal-llms-anthropic\n",
|
||||
"!pip install llama-index-embeddings-huggingface\n",
|
||||
"!pip install llama-cloud-services"
|
||||
"%pip install llama-index\n",
|
||||
"%pip install llama-index-core\n",
|
||||
"%pip install llama-index-llms-anthropic\n",
|
||||
"%pip install llama-index-embeddings-huggingface\n",
|
||||
"%pip install llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -45,11 +45,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the async code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# API access to llama-cloud\n",
|
||||
@@ -68,7 +63,7 @@
|
||||
"source": [
|
||||
"from llama_index.llms.anthropic import Anthropic\n",
|
||||
"\n",
|
||||
"llm = Anthropic(model=\"claude-3-opus-20240229\", temperature=0.0)"
|
||||
"llm = Anthropic(model=\"claude-3-5-sonnet-20241022\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -131,28 +126,8 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(verbose=True)\n",
|
||||
"json_objs = parser.get_json_result(\"./uber_10q_march_2022.pdf\")\n",
|
||||
"json_list = json_objs[0][\"pages\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b26d21d1-05b5-4f49-b937-c13106a84015",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.schema import TextNode\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_text_nodes(json_list: List[dict]):\n",
|
||||
" text_nodes = []\n",
|
||||
" for idx, page in enumerate(json_list):\n",
|
||||
" text_node = TextNode(text=page[\"text\"], metadata={\"page\": page[\"page\"]})\n",
|
||||
" text_nodes.append(text_node)\n",
|
||||
" return text_nodes"
|
||||
"parser = LlamaParse(take_screenshot=True)\n",
|
||||
"result = await parser.aparse(\"./uber_10q_march_2022.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -162,7 +137,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"text_nodes = get_text_nodes(json_list)"
|
||||
"text_nodes = await result.aget_text_nodes(split_by_page=True)\n",
|
||||
"image_nodes = await result.aget_image_nodes(\n",
|
||||
" include_screenshot_images=True,\n",
|
||||
" include_object_images=True,\n",
|
||||
" image_download_dir=\"./uber_10q_images\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -172,7 +152,7 @@
|
||||
"source": [
|
||||
"## Extract/Index images from image dicts\n",
|
||||
"\n",
|
||||
"Here we use a multimodal model to extract and index images from image dictionaries."
|
||||
"Here we use a multimodal model to caption images and create text nodes for indexing."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -190,27 +170,32 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# call get_images on parser, convert to ImageDocuments\n",
|
||||
"!mkdir llama2_images\n",
|
||||
"!mkdir -p llama2_images\n",
|
||||
"\n",
|
||||
"from llama_index.core.schema import ImageDocument\n",
|
||||
"from llama_index.multi_modal_llms.anthropic import AnthropicMultiModal\n",
|
||||
"from llama_index.core.llms import ChatMessage, ImageBlock, TextBlock\n",
|
||||
"from llama_index.core.schema import ImageNode, TextNode\n",
|
||||
"from llama_index.llms.anthropic import Anthropic\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_image_text_nodes(json_objs: List[dict]):\n",
|
||||
"def get_image_text_nodes(image_nodes: list[ImageNode]):\n",
|
||||
" \"\"\"Extract out text from images using a multimodal model.\"\"\"\n",
|
||||
" anthropic_mm_llm = AnthropicMultiModal(max_tokens=300)\n",
|
||||
" image_dicts = parser.get_images(json_objs, download_path=\"llama2_images\")\n",
|
||||
" image_documents = []\n",
|
||||
" llm = Anthropic(model=\"claude-3-5-haiku-20241022\", max_tokens=300)\n",
|
||||
" img_text_nodes = []\n",
|
||||
" for image_dict in image_dicts:\n",
|
||||
" image_doc = ImageDocument(image_path=image_dict[\"path\"])\n",
|
||||
" response = anthropic_mm_llm.complete(\n",
|
||||
" prompt=\"Describe the images as alt text\",\n",
|
||||
" image_documents=[image_doc],\n",
|
||||
" for image_node in image_nodes:\n",
|
||||
" image_path = image_node.image_path\n",
|
||||
" message = ChatMessage(\n",
|
||||
" role=\"user\",\n",
|
||||
" blocks=[\n",
|
||||
" TextBlock(text=\"Describe the images as alt text\"),\n",
|
||||
" ImageBlock(path=image_path),\n",
|
||||
" ],\n",
|
||||
" )\n",
|
||||
" response = llm.chat([message])\n",
|
||||
" text_node = TextNode(\n",
|
||||
" text=str(response.message.content), metadata={\"path\": image_path}\n",
|
||||
" )\n",
|
||||
" text_node = TextNode(text=str(response), metadata={\"path\": image_dict[\"path\"]})\n",
|
||||
" img_text_nodes.append(text_node)\n",
|
||||
"\n",
|
||||
" return img_text_nodes"
|
||||
]
|
||||
},
|
||||
@@ -221,7 +206,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"image_text_nodes = get_image_text_nodes(json_objs)"
|
||||
"image_text_nodes = get_image_text_nodes(image_nodes)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
+209
-659
File diff suppressed because it is too large
Load Diff
@@ -31,14 +31,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the sync code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-...\""
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-...\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -79,8 +74,9 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(result_type=\"text\", language=\"fr\")\n",
|
||||
"documents = parser.load_data(\"./treasury_report.pdf\")"
|
||||
"parser = LlamaParse(language=\"fr\")\n",
|
||||
"result = await parser.aparse(\"./treasury_report.pdf\")\n",
|
||||
"documents = result.get_text_documents(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -252,8 +248,9 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(result_type=\"text\", language=\"ch_sim\")\n",
|
||||
"documents = parser.load_data(\"./chinese_pdf.pdf\")"
|
||||
"parser = LlamaParse(language=\"ch_sim\")\n",
|
||||
"result = await parser.aparse(\"./chinese_pdf.pdf\")\n",
|
||||
"documents = result.get_text_documents(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -406,8 +403,9 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"base_parser = LlamaParse(result_type=\"text\", language=\"en\")\n",
|
||||
"base_documents = parser.load_data(\"./chinese_pdf2.pdf\")"
|
||||
"base_parser = LlamaParse(language=\"en\")\n",
|
||||
"result = await base_parser.aparse(\"./chinese_pdf2.pdf\")\n",
|
||||
"base_documents = result.get_text_documents(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -60,11 +60,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the sync code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import requests\n",
|
||||
"import pymongo\n",
|
||||
"\n",
|
||||
@@ -72,7 +67,7 @@
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"from llama_index.embeddings.openai import OpenAIEmbedding\n",
|
||||
"from llama_index.core import VectorStoreIndex, StorageContext\n",
|
||||
"from llama_index.core.node_parser import SimpleNodeParser"
|
||||
"from llama_index.core.node_parser import SentenceSplitter"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -137,7 +132,8 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"documents = LlamaParse(result_type=\"text\").load_data(file_path)"
|
||||
"result = await LlamaParse().aparse(file_path)\n",
|
||||
"documents = result.get_text_documents(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -203,7 +199,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"node_parser = SimpleNodeParser()\n",
|
||||
"node_parser = SentenceSplitter()\n",
|
||||
"\n",
|
||||
"nodes = node_parser.get_nodes_from_documents(documents)"
|
||||
]
|
||||
|
||||
@@ -1,544 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# LlamaParse - Parsing comic books with parsing intructions\n",
|
||||
"Parsing intructions allow you to instruct our parsing model the same way you would instruct an LLM!\n",
|
||||
"\n",
|
||||
"They can be useful to help the parser get better results on complex document layouts, to extract data in a specific format, or to transform the document in other ways.\n",
|
||||
"\n",
|
||||
"Using Parsing Instruction you will get better results out of LlamaParse on complicated documents, and also be able to simplify your application code."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Installation\n",
|
||||
"\n",
|
||||
"Parsing instructions are part of the llamaParse API. They can be accessed by directly specifying the parsing_instruction parameter in the API or by using the LlamaParse python module (which we will use for this tutorial).\n",
|
||||
"\n",
|
||||
"To install llama-parse, just get it from PIP:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Collecting llama-parse\n",
|
||||
" Downloading llama_parse-0.3.8-py3-none-any.whl (6.7 kB)\n",
|
||||
"Collecting llama-index-core>=0.10.7 (from llama-parse)\n",
|
||||
" Downloading llama_index_core-0.10.19-py3-none-any.whl (15.3 MB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m15.3/15.3 MB\u001b[0m \u001b[31m31.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: PyYAML>=6.0.1 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (6.0.1)\n",
|
||||
"Requirement already satisfied: SQLAlchemy[asyncio]>=1.4.49 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (2.0.28)\n",
|
||||
"Requirement already satisfied: aiohttp<4.0.0,>=3.8.6 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (3.9.3)\n",
|
||||
"Collecting dataclasses-json (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading dataclasses_json-0.6.4-py3-none-any.whl (28 kB)\n",
|
||||
"Collecting deprecated>=1.2.9.3 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading Deprecated-1.2.14-py2.py3-none-any.whl (9.6 kB)\n",
|
||||
"Collecting dirtyjson<2.0.0,>=1.0.8 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading dirtyjson-1.0.8-py3-none-any.whl (25 kB)\n",
|
||||
"Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (2023.6.0)\n",
|
||||
"Collecting httpx (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading httpx-0.27.0-py3-none-any.whl (75 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m75.6/75.6 kB\u001b[0m \u001b[31m6.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hCollecting llamaindex-py-client<0.2.0,>=0.1.13 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading llamaindex_py_client-0.1.13-py3-none-any.whl (107 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m108.0/108.0 kB\u001b[0m \u001b[31m10.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: nest-asyncio<2.0.0,>=1.5.8 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (1.6.0)\n",
|
||||
"Requirement already satisfied: networkx>=3.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (3.2.1)\n",
|
||||
"Requirement already satisfied: nltk<4.0.0,>=3.8.1 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (3.8.1)\n",
|
||||
"Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (1.25.2)\n",
|
||||
"Collecting openai>=1.1.0 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading openai-1.13.3-py3-none-any.whl (227 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m227.4/227.4 kB\u001b[0m \u001b[31m16.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (1.5.3)\n",
|
||||
"Requirement already satisfied: pillow>=9.0.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (9.4.0)\n",
|
||||
"Requirement already satisfied: requests>=2.31.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (2.31.0)\n",
|
||||
"Requirement already satisfied: tenacity<9.0.0,>=8.2.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (8.2.3)\n",
|
||||
"Collecting tiktoken>=0.3.3 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading tiktoken-0.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.8 MB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.8/1.8 MB\u001b[0m \u001b[31m43.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: tqdm<5.0.0,>=4.66.1 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (4.66.2)\n",
|
||||
"Requirement already satisfied: typing-extensions>=4.5.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (4.10.0)\n",
|
||||
"Collecting typing-inspect>=0.8.0 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)\n",
|
||||
"Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (1.3.1)\n",
|
||||
"Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (23.2.0)\n",
|
||||
"Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (1.4.1)\n",
|
||||
"Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (6.0.5)\n",
|
||||
"Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (1.9.4)\n",
|
||||
"Requirement already satisfied: async-timeout<5.0,>=4.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (4.0.3)\n",
|
||||
"Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.10/dist-packages (from deprecated>=1.2.9.3->llama-index-core>=0.10.7->llama-parse) (1.14.1)\n",
|
||||
"Requirement already satisfied: pydantic>=1.10 in /usr/local/lib/python3.10/dist-packages (from llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core>=0.10.7->llama-parse) (2.6.3)\n",
|
||||
"Requirement already satisfied: anyio in /usr/local/lib/python3.10/dist-packages (from httpx->llama-index-core>=0.10.7->llama-parse) (3.7.1)\n",
|
||||
"Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx->llama-index-core>=0.10.7->llama-parse) (2024.2.2)\n",
|
||||
"Collecting httpcore==1.* (from httpx->llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading httpcore-1.0.4-py3-none-any.whl (77 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m77.8/77.8 kB\u001b[0m \u001b[31m8.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: idna in /usr/local/lib/python3.10/dist-packages (from httpx->llama-index-core>=0.10.7->llama-parse) (3.6)\n",
|
||||
"Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from httpx->llama-index-core>=0.10.7->llama-parse) (1.3.1)\n",
|
||||
"Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx->llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m5.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: click in /usr/local/lib/python3.10/dist-packages (from nltk<4.0.0,>=3.8.1->llama-index-core>=0.10.7->llama-parse) (8.1.7)\n",
|
||||
"Requirement already satisfied: joblib in /usr/local/lib/python3.10/dist-packages (from nltk<4.0.0,>=3.8.1->llama-index-core>=0.10.7->llama-parse) (1.3.2)\n",
|
||||
"Requirement already satisfied: regex>=2021.8.3 in /usr/local/lib/python3.10/dist-packages (from nltk<4.0.0,>=3.8.1->llama-index-core>=0.10.7->llama-parse) (2023.12.25)\n",
|
||||
"Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai>=1.1.0->llama-index-core>=0.10.7->llama-parse) (1.7.0)\n",
|
||||
"Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests>=2.31.0->llama-index-core>=0.10.7->llama-parse) (3.3.2)\n",
|
||||
"Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.31.0->llama-index-core>=0.10.7->llama-parse) (2.0.7)\n",
|
||||
"Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy[asyncio]>=1.4.49->llama-index-core>=0.10.7->llama-parse) (3.0.3)\n",
|
||||
"Collecting mypy-extensions>=0.3.0 (from typing-inspect>=0.8.0->llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)\n",
|
||||
"Collecting marshmallow<4.0.0,>=3.18.0 (from dataclasses-json->llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading marshmallow-3.21.1-py3-none-any.whl (49 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.4/49.4 kB\u001b[0m \u001b[31m4.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.10/dist-packages (from pandas->llama-index-core>=0.10.7->llama-parse) (2.8.2)\n",
|
||||
"Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->llama-index-core>=0.10.7->llama-parse) (2023.4)\n",
|
||||
"Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio->httpx->llama-index-core>=0.10.7->llama-parse) (1.2.0)\n",
|
||||
"Requirement already satisfied: packaging>=17.0 in /usr/local/lib/python3.10/dist-packages (from marshmallow<4.0.0,>=3.18.0->dataclasses-json->llama-index-core>=0.10.7->llama-parse) (23.2)\n",
|
||||
"Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic>=1.10->llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core>=0.10.7->llama-parse) (0.6.0)\n",
|
||||
"Requirement already satisfied: pydantic-core==2.16.3 in /usr/local/lib/python3.10/dist-packages (from pydantic>=1.10->llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core>=0.10.7->llama-parse) (2.16.3)\n",
|
||||
"Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.8.1->pandas->llama-index-core>=0.10.7->llama-parse) (1.16.0)\n",
|
||||
"Installing collected packages: dirtyjson, mypy-extensions, marshmallow, h11, deprecated, typing-inspect, tiktoken, httpcore, httpx, dataclasses-json, openai, llamaindex-py-client, llama-index-core, llama-parse\n",
|
||||
"Successfully installed dataclasses-json-0.6.4 deprecated-1.2.14 dirtyjson-1.0.8 h11-0.14.0 httpcore-1.0.4 httpx-0.27.0 llama-index-core-0.10.19 llama-parse-0.3.8 llamaindex-py-client-0.1.13 marshmallow-3.21.1 mypy-extensions-1.0.0 openai-1.13.3 tiktoken-0.6.0 typing-inspect-0.9.0\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%pip install llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## API key\n",
|
||||
"\n",
|
||||
"The use of LlamaParse requires an API key which you can get here: https://cloud.llamaindex.ai/parse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-...\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Async (Notebook only)\n",
|
||||
"llama-parse is async-first, so running the code in a notebook requires the use of nest_asyncio\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Import the package"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Using llamaparse for getting better results (on Manga!)\n",
|
||||
"\n",
|
||||
"Sometimes the layout of a page is unusual and you will get sub-optimal reading order results with LlamaParse. For example, when parsing manga you expect the reading order to be right to left even if the content is in English!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's download an extract of a great manga \"The manga guide to calculus\", by Hiroyuki Kojima (https://www.amazon.com/Manga-Guide-Calculus-Hiroyuki-Kojima/dp/1593271948)\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--2024-03-13 13:57:19-- https://drive.usercontent.google.com/uc?id=1tZJhcpepLRdQFJFCFX50QIqLyLgqzZsY&export=download\n",
|
||||
"Resolving drive.usercontent.google.com (drive.usercontent.google.com)... 173.194.211.132, 2607:f8b0:400c:c10::84\n",
|
||||
"Connecting to drive.usercontent.google.com (drive.usercontent.google.com)|173.194.211.132|:443... connected.\n",
|
||||
"HTTP request sent, awaiting response... 303 See Other\n",
|
||||
"Location: https://drive.usercontent.google.com/download?id=1tZJhcpepLRdQFJFCFX50QIqLyLgqzZsY&export=download [following]\n",
|
||||
"--2024-03-13 13:57:19-- https://drive.usercontent.google.com/download?id=1tZJhcpepLRdQFJFCFX50QIqLyLgqzZsY&export=download\n",
|
||||
"Reusing existing connection to drive.usercontent.google.com:443.\n",
|
||||
"HTTP request sent, awaiting response... 200 OK\n",
|
||||
"Length: 3041634 (2.9M) [application/octet-stream]\n",
|
||||
"Saving to: ‘./manga.pdf’\n",
|
||||
"\n",
|
||||
"./manga.pdf 100%[===================>] 2.90M --.-KB/s in 0.04s \n",
|
||||
"\n",
|
||||
"2024-03-13 13:57:20 (78.6 MB/s) - ‘./manga.pdf’ saved [3041634/3041634]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"! wget \"https://drive.usercontent.google.com/uc?id=1tZJhcpepLRdQFJFCFX50QIqLyLgqzZsY&export=download\" -O ./manga.pdf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Without parsing instructions\n",
|
||||
"For the sake of comparison, let's first parse without any instructions."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id 25bf4202-78d8-4705-88cf-c616ae7c82af\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vanilaParsing = LlamaParse(result_type=\"markdown\").load_data(\"./manga.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"As you can see below, LlamaParse is not doing a great job here. It is interpreting the grid of comic panels as a table, and trying to fit the dialogue into a table. It's very hard to follow."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"The Asagake Times Sanda-Cho Distributor\n",
|
||||
"\n",
|
||||
"A newspaper distributor? do I have the wrong map?\n",
|
||||
"\n",
|
||||
"You’re looking It’s next for the Sanda-cho door. branch office? Everybody mistakes us for the office because we are larger. What Is a Function? 3\n",
|
||||
"---\n",
|
||||
"## Calculating the Derivative of a Constant, Linear, or Quadratic Function\n",
|
||||
"\n",
|
||||
"|1.|Let’s find the derivative of constant function f(x) = α. The differential coefficient of f(x) at x = a is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 (f(a + ε) - f(a)) / ε = lim ε→0 (α - α) = lim ε→0 0 = 0|\n",
|
||||
"| |Thus, the derivative of f(x) is f′(x) = 0. This makes sense, since our function is constant—the rate of change is 0.|\n",
|
||||
"\n",
|
||||
"Note: The differential coefficient of f(x) at x = a is often simply called the derivative of f(x) at x = a, or just f′(a).\n",
|
||||
"\n",
|
||||
"|2.|Let’s calculate the derivative of linear function f(x) = αx + β. The derivative of f(x) at x = α is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 (f(α + ε) - f(a)) = \n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(vanilaParsing[0].text[100:1000])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Using parsing instructions\n",
|
||||
"Let's try to parse the manga with custom instructions:\n",
|
||||
"\n",
|
||||
"\"The provided document is a manga comic book. Most pages do NOT have a title. It does not contain tables. Try to reconstruct the dialogue spoken in a cohesive way.\"\n",
|
||||
"\n",
|
||||
"To do so just pass the parsing instruction as a parameter to LlamaParse:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id 88ab273e-b2a7-4f84-8e72-e9367cf6b114\n",
|
||||
"."
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parsingInstructionManga = \"\"\"The provided document is a manga comic book. Most pages do NOT have a title.\n",
|
||||
"It does not contain tables.\n",
|
||||
"Try to reconstruct the dialogue spoken in a cohesive way.\"\"\"\n",
|
||||
"withInstructionParsing = LlamaParse(\n",
|
||||
" result_type=\"markdown\", parsing_instruction=parsingInstructionManga\n",
|
||||
").load_data(\"./manga.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's see how it compare with page 3! We encourage you to play with the target page and explore other pages. As you will see, the parsing instruction allowed LlamaParse to make sense of the document!\n",
|
||||
"\n",
|
||||
"<img src=\"https://drive.usercontent.google.com/download?id=1M87rXTIZE8d5v7aHmVZVW6gW3eDGq6ks&authuser=0\" />\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The Asagake Times Sanda-Cho Distributor\n",
|
||||
"\n",
|
||||
"A newspaper distributor? do I have the wrong map?\n",
|
||||
"\n",
|
||||
"You’re looking It’s next for the Sanda-cho door. branch office? Everybody mistakes us for the office because we are larger. What Is a Function? 3\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"------------------------------------------------------------\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# The Asagake Times\n",
|
||||
"\n",
|
||||
"Sanda-Cho Distributor\n",
|
||||
"\n",
|
||||
"A newspaper distributor?\n",
|
||||
"\n",
|
||||
"Do I have the wrong map?\n",
|
||||
"\n",
|
||||
"You're looking for the Sanda-cho branch office?\n",
|
||||
"\n",
|
||||
"It's next door.\n",
|
||||
"\n",
|
||||
"Everybody mistakes us for the office because we are larger.\n",
|
||||
"\n",
|
||||
"What Is a Function? 3\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"target_page = 1\n",
|
||||
"print(vanilaParsing[0].text.split(\"\\n---\\n\")[target_page])\n",
|
||||
"print(\"\\n\\n------------------------------------------------------------\\n\\n\")\n",
|
||||
"print(withInstructionParsing[0].text.split(\"\\n---\\n\")[target_page])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Math - doing more with parsing instuction!\n",
|
||||
"\n",
|
||||
"But this manga is about math and full of equations, why not ask the parser to output them in **LaTeX**?\n",
|
||||
"\n",
|
||||
"<img src=\"https://drive.usercontent.google.com/download?id=1tze3xcQ7axVA-vC_iZeAj_GvYcyNuYDa&authuser=0\" />"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id 3a055e64-d91e-484e-b9b0-99a2e637c08d\n",
|
||||
"."
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parsingInstructionMangaLatex = \"\"\"The provided document is a manga comic book. Most pages do NOT have a title.\n",
|
||||
"It does not contain tables.\n",
|
||||
"Try to reconstruct the dialogue spoken in a cohesive way.\n",
|
||||
"Output any math equation in LATEX markdown (between $$)\"\"\"\n",
|
||||
"withLatex = LlamaParse(\n",
|
||||
" result_type=\"markdown\", parsing_instruction=parsingInstructionMangaLatex\n",
|
||||
").load_data(\"./manga.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"[Without instruction]------------------------------------------------------------\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Calculating the Derivative of a Constant, Linear, or Quadratic Function\n",
|
||||
"\n",
|
||||
"|1.|Let’s find the derivative of constant function f(x) = α. The differential coefficient of f(x) at x = a is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 (f(a + ε) - f(a)) / ε = lim ε→0 (α - α) = lim ε→0 0 = 0|\n",
|
||||
"| |Thus, the derivative of f(x) is f′(x) = 0. This makes sense, since our function is constant—the rate of change is 0.|\n",
|
||||
"\n",
|
||||
"Note: The differential coefficient of f(x) at x = a is often simply called the derivative of f(x) at x = a, or just f′(a).\n",
|
||||
"\n",
|
||||
"|2.|Let’s calculate the derivative of linear function f(x) = αx + β. The derivative of f(x) at x = α is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 (f(α + ε) - f(a)) = lim ε→0 (α(a + ε) + β - (αa + β)) = lim ε→0 α = α|\n",
|
||||
"| |Thus, the derivative of f(x) is f′(x) = α, a constant value. This result should also be intuitive—linear functions have a constant rate of change by definition.|\n",
|
||||
"\n",
|
||||
"|3.|Let’s find the derivative of f(x) = x^2, which appeared in the story. The differential coefficient of f(x) at x = a is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 ((a + ε)^2 - a^2) / ε = lim (a^2 + 2aε + ε^2 - a^2) / ε = lim (2aε + ε^2) = lim (2a + ε) = 2a|\n",
|
||||
"| |Thus, the differential coefficient of f(x) at x = a is 2a, or f′(a) = 2a. Therefore, the derivative of f(x) is f′(x) = 2x.|\n",
|
||||
"\n",
|
||||
"## Summary\n",
|
||||
"\n",
|
||||
"- The calculation of a limit that appears in calculus is simply a formula calculating an error.\n",
|
||||
"- A limit is used to obtain a derivative.\n",
|
||||
"- The derivative is the slope of the tangent line at a given point.\n",
|
||||
"- The derivative is nothing but the rate of change.\n",
|
||||
"\n",
|
||||
"## Chapter 1 Let’s Differentiate a Function!\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"[With instruction to output math in LATEX!]------------------------------------------------------------\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Derivative of Constant, Linear, or Quadratic Function\n",
|
||||
"\n",
|
||||
"## Calculating the Derivative of a Constant, Linear, or Quadratic Function\n",
|
||||
"\n",
|
||||
"1. Let’s find the derivative of constant function f(x) = α. The differential coefficient of f(x) at x = a is\n",
|
||||
"\n",
|
||||
"$$\n",
|
||||
"\\begin{align*}\n",
|
||||
"&\\lim_{{\\varepsilon \\to 0}} \\left( \\frac{f(a + \\varepsilon) - f(a)}{\\varepsilon} \\right) = \\lim_{{\\varepsilon \\to 0}} \\frac{\\alpha - \\alpha}{\\varepsilon} = \\lim_{{\\varepsilon \\to 0}} 0 = 0 \\\\\n",
|
||||
"\\end{align*}\n",
|
||||
"$$\n",
|
||||
"Thus, the derivative of f(x) is f′(x) = 0. This makes sense, since our function is constant—the rate of change is 0.\n",
|
||||
"\n",
|
||||
"Note: The differential coefficient of f(x) at x = a is often simply called the derivative of f(x) at x = a, or just f′(a).\n",
|
||||
"\n",
|
||||
"2. Let’s calculate the derivative of linear function f(x) = αx + β. The derivative of f(x) at x = α is\n",
|
||||
"\n",
|
||||
"$$\n",
|
||||
"\\begin{align*}\n",
|
||||
"&\\lim_{{\\varepsilon \\to 0}} \\left( \\frac{f(\\alpha + \\varepsilon) - f(a)}{\\varepsilon} \\right) = \\lim_{{\\varepsilon \\to 0}} \\frac{\\alpha(a + \\varepsilon) + \\beta - (\\alpha a + \\beta)}{\\varepsilon} = \\lim_{{\\varepsilon \\to 0}} \\alpha = \\alpha \\\\\n",
|
||||
"\\end{align*}\n",
|
||||
"$$\n",
|
||||
"Thus, the derivative of f(x) is f′(x) = α, a constant value. This result should also be intuitive—linear functions have a constant rate of change by definition.\n",
|
||||
"\n",
|
||||
"3. Let’s find the derivative of f(x) = x2. The differential coefficient of f(x) at x = a is\n",
|
||||
"\n",
|
||||
"$$\n",
|
||||
"\\begin{align*}\n",
|
||||
"&\\lim_{{\\varepsilon \\to 0}} \\left( \\frac{f(a + \\varepsilon) - f(a)}{\\varepsilon} \\right) = \\lim_{{\\varepsilon \\to 0}} \\left( (a + \\varepsilon)^2 - a^2 \\right) = \\lim_{{\\varepsilon \\to 0}} 2a\\varepsilon + \\varepsilon = \\lim_{{\\varepsilon \\to 0}} (2a + \\varepsilon) = 2a \\\\\n",
|
||||
"\\end{align*}\n",
|
||||
"$$\n",
|
||||
"Thus, the differential coefficient of f(x) at x = a is 2a, or f′(a) = 2a. Therefore, the derivative of f(x) is f′(x) = 2x.\n",
|
||||
"\n",
|
||||
"### Summary\n",
|
||||
"\n",
|
||||
"- The calculation of a limit that appears in calculus is simply a formula calculating an error.\n",
|
||||
"- A limit is used to obtain a derivative.\n",
|
||||
"- The derivative is the slope of the tangent line at a given point.\n",
|
||||
"- The derivative is nothing but the rate of change.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"target_page = 2\n",
|
||||
"print(\n",
|
||||
" \"\\n\\n[Without instruction]------------------------------------------------------------\\n\\n\"\n",
|
||||
")\n",
|
||||
"print(vanilaParsing[0].text.split(\"\\n---\\n\")[target_page])\n",
|
||||
"print(\n",
|
||||
" \"\\n\\n[With instruction to output math in LATEX!]------------------------------------------------------------\\n\\n\"\n",
|
||||
")\n",
|
||||
"print(withLatex[0].text.split(\"\\n---\\n\")[target_page])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And here is the result as rendered by https://upmath.me/ .\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"<img src=\"https://drive.usercontent.google.com/download?id=1qGo5bMGYOiIC9MnprcgEByaYjU9YII2Q&authuser=0\" />\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Over this short notebook we saw how to use parsing instructions to increase the quality and accuracy of parsing with LLamaParse!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"provenance": []
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -55,10 +55,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# API access to llama-cloud\n",
|
||||
@@ -80,25 +76,7 @@
|
||||
"execution_count": null,
|
||||
"id": "IjtKDQRLrylI",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--2024-12-05 18:54:24-- https://arxiv.org/pdf/2409.18486\n",
|
||||
"Resolving arxiv.org (arxiv.org)... 151.101.67.42, 151.101.131.42, 151.101.3.42, ...\n",
|
||||
"Connecting to arxiv.org (arxiv.org)|151.101.67.42|:443... connected.\n",
|
||||
"HTTP request sent, awaiting response... 200 OK\n",
|
||||
"Length: 13986265 (13M) [application/pdf]\n",
|
||||
"Saving to: ‘o1.pdf’\n",
|
||||
"\n",
|
||||
"o1.pdf 100%[===================>] 13.34M 11.8MB/s in 1.1s \n",
|
||||
"\n",
|
||||
"2024-12-05 18:54:26 (11.8 MB/s) - ‘o1.pdf’ saved [13986265/13986265]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget \"https://arxiv.org/pdf/2409.18486\" -O \"o1.pdf\""
|
||||
]
|
||||
@@ -118,25 +96,6 @@
|
||||
"Using your own API key may incur additional costs from your model provider and could result in failed pages or documents if you do not have sufficient usage limits."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dc921729-3446-42ca-8e1b-a6fd26195ed9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.schema import TextNode\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_text_nodes(json_list: List[dict]):\n",
|
||||
" text_nodes = []\n",
|
||||
" for idx, page in enumerate(json_list):\n",
|
||||
" text_node = TextNode(text=page[\"md\"], metadata={\"page\": page[\"page\"]})\n",
|
||||
" text_nodes.append(text_node)\n",
|
||||
" return text_nodes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1b5d6da6",
|
||||
@@ -163,15 +122,13 @@
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(\n",
|
||||
" result_type=\"markdown\",\n",
|
||||
" use_vendor_multimodal_model=True,\n",
|
||||
" vendor_multimodal_model_name=\"anthropic-sonnet-3.5\",\n",
|
||||
" target_pages=\"24\"\n",
|
||||
" # invalidate_cache=True\n",
|
||||
")\n",
|
||||
"json_objs = parser.get_json_result(\"o1.pdf\")\n",
|
||||
"json_list = json_objs[0][\"pages\"]\n",
|
||||
"docs = get_text_nodes(json_list)"
|
||||
"result = await parser.aparse(\"o1.pdf\")\n",
|
||||
"nodes = result.get_text_nodes(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -202,15 +159,13 @@
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser_gpt4o = LlamaParse(\n",
|
||||
" result_type=\"markdown\",\n",
|
||||
" use_vendor_multimodal_model=True,\n",
|
||||
" vendor_multimodal_model=\"openai-gpt4o\",\n",
|
||||
" target_pages=\"24\",\n",
|
||||
" # invalidate_cache=True\n",
|
||||
")\n",
|
||||
"json_objs_gpt4o = parser_gpt4o.get_json_result(\"o1.pdf\")\n",
|
||||
"json_list_gpt4o = json_objs_gpt4o[0][\"pages\"]\n",
|
||||
"docs_gpt4o = get_text_nodes(json_list_gpt4o)"
|
||||
"result = await parser_gpt4o.aparse(\"o1.pdf\")\n",
|
||||
"nodes = result.get_markdown_nodes(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -268,7 +223,7 @@
|
||||
],
|
||||
"source": [
|
||||
"# using Sonnet-3.5\n",
|
||||
"print(docs[0].get_content(metadata_mode=\"all\"))"
|
||||
"print(nodes[0].get_content(metadata_mode=\"all\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -327,7 +282,7 @@
|
||||
],
|
||||
"source": [
|
||||
"# using GPT-4o\n",
|
||||
"print(docs_gpt4o[0].get_content(metadata_mode=\"all\"))"
|
||||
"print(nodes[0].get_content(metadata_mode=\"all\"))"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -47,11 +47,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the async code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# API access to llama-cloud\n",
|
||||
@@ -71,25 +66,7 @@
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--2024-12-05 11:40:59-- https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/10k/uber_2021.pdf\n",
|
||||
"Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 2606:50c0:8000::154, 2606:50c0:8002::154, 2606:50c0:8003::154, ...\n",
|
||||
"Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|2606:50c0:8000::154|:443... connected.\n",
|
||||
"HTTP request sent, awaiting response... 200 OK\n",
|
||||
"Length: 1880483 (1.8M) [application/octet-stream]\n",
|
||||
"Saving to: ‘./uber_2021.pdf’\n",
|
||||
"\n",
|
||||
"./uber_2021.pdf 100%[===================>] 1.79M --.-KB/s in 0.1s \n",
|
||||
"\n",
|
||||
"2024-12-05 11:40:59 (14.2 MB/s) - ‘./uber_2021.pdf’ saved [1880483/1880483]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/10k/uber_2021.pdf' -O './uber_2021.pdf'"
|
||||
]
|
||||
@@ -119,9 +96,10 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(target_pages=\"0,1,2\", result_type=\"markdown\")\n",
|
||||
"parser = LlamaParse(target_pages=\"0,1,2\")\n",
|
||||
"\n",
|
||||
"documents = parser.load_data(\"./uber_2021.pdf\")"
|
||||
"results = await parser.aparse(\"./uber_2021.pdf\")\n",
|
||||
"documents = results.get_text_documents(split_by_page=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,367 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# RAG for Table Comparisons with LlamaParse + LlamaIndex\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/parse/demo_table_comparisons.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"\n",
|
||||
"This notebook shows you how to do comparisons across both tabular and text data across multiple PDF documents.\n",
|
||||
"\n",
|
||||
"We load in multiple PDFs with embedded tables (2021 and 2020 10K filings for Apple) using LlamaParse, parse each into a hierarchy of tables/text objects, define a recursive retriever over each, and then compose both with a SubQuestionQueryEngine."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"Install core packages, download files, parse documents."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install llama-index\n",
|
||||
"%pip install llama-index-core\n",
|
||||
"%pip install llama-index-embeddings-openai\n",
|
||||
"%pip install llama-index-question-gen-openai\n",
|
||||
"%pip install llama-index-postprocessor-flag-embedding-reranker\n",
|
||||
"%pip install git+https://github.com/FlagOpen/FlagEmbedding.git\n",
|
||||
"%pip install llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget \"https://s2.q4cdn.com/470004039/files/doc_financials/2020/ar/_10-K-2020-(As-Filed).pdf\" -O apple_2020_10k.pdf\n",
|
||||
"!wget \"https://s2.q4cdn.com/470004039/files/doc_financials/2021/q4/_10-K-2021-(As-Filed).pdf\" -O apple_2021_10k.pdf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Some OpenAI and LlamaParse details"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the async code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# API access to llama-cloud\n",
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-\"\n",
|
||||
"\n",
|
||||
"# Using OpenAI API for embeddings/llms\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = \"sk-\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.llms.openai import OpenAI\n",
|
||||
"from llama_index.embeddings.openai import OpenAIEmbedding\n",
|
||||
"from llama_index.core import VectorStoreIndex\n",
|
||||
"from llama_index.core import Settings\n",
|
||||
"\n",
|
||||
"embed_model = OpenAIEmbedding(model=\"text-embedding-3-small\")\n",
|
||||
"llm = OpenAI(model=\"gpt-3.5-turbo-0125\")\n",
|
||||
"\n",
|
||||
"Settings.llm = llm\n",
|
||||
"Settings.embed_model = embed_model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Using brand new `LlamaParse` PDF reader for PDF Parsing\n",
|
||||
"\n",
|
||||
"we also compare two different retrieval/query engine strategies:\n",
|
||||
"1. Using raw Markdown text as nodes for building index and apply simple query engine for generating the results;\n",
|
||||
"2. Using `MarkdownElementNodeParser` for parsing the `LlamaParse` output Markdown results and building recursive retriever query engine for generation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"docs_2021 = LlamaParse(result_type=\"markdown\").load_data(\"./apple_2021_10k.pdf\")\n",
|
||||
"docs_2020 = LlamaParse(result_type=\"markdown\").load_data(\"./apple_2020_10k.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Create Recursive Retriever over each Document\n",
|
||||
"\n",
|
||||
"We define a function to get a recursive retriever from each document. The steps are the following:\n",
|
||||
"- Hierarchically parse the document using our `MarkdownElementNodeParser`, which will embed/summarize embedded tables.\n",
|
||||
"- Load into a vector store. Under the hood we will automatically store links between nodes (e.g. table summary to table text).\n",
|
||||
"- Get a query engine over the vector store, which performs retrieval/synthesis. Under the hood we will automatically perform recursive retrieval if there are links."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.node_parser import MarkdownElementNodeParser\n",
|
||||
"\n",
|
||||
"node_parser = MarkdownElementNodeParser(\n",
|
||||
" llm=OpenAI(model=\"gpt-3.5-turbo-0125\"), num_workers=8\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pickle\n",
|
||||
"from llama_index.postprocessor.flag_embedding_reranker import (\n",
|
||||
" FlagEmbeddingReranker,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"reranker = FlagEmbeddingReranker(\n",
|
||||
" top_n=5,\n",
|
||||
" model=\"BAAI/bge-reranker-large\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def create_query_engine_over_doc(docs, nodes_save_path=None):\n",
|
||||
" \"\"\"Big function to go from document path -> recursive retriever.\"\"\"\n",
|
||||
" if nodes_save_path is not None and os.path.exists(nodes_save_path):\n",
|
||||
" raw_nodes = pickle.load(open(nodes_save_path, \"rb\"))\n",
|
||||
" else:\n",
|
||||
" raw_nodes = node_parser.get_nodes_from_documents(docs)\n",
|
||||
" if nodes_save_path is not None:\n",
|
||||
" pickle.dump(raw_nodes, open(nodes_save_path, \"wb\"))\n",
|
||||
"\n",
|
||||
" base_nodes, objects = node_parser.get_nodes_and_objects(raw_nodes)\n",
|
||||
"\n",
|
||||
" ### Construct Retrievers\n",
|
||||
" # construct top-level vector index + query engine\n",
|
||||
" vector_index = VectorStoreIndex(nodes=base_nodes + objects)\n",
|
||||
" query_engine = vector_index.as_query_engine(\n",
|
||||
" similarity_top_k=15, node_postprocessors=[reranker]\n",
|
||||
" )\n",
|
||||
" return query_engine, base_nodes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"query_engine_2021, nodes_2021 = create_query_engine_over_doc(\n",
|
||||
" docs_2021, nodes_save_path=\"2021_nodes.pkl\"\n",
|
||||
")\n",
|
||||
"query_engine_2020, nodes_2020 = create_query_engine_over_doc(\n",
|
||||
" docs_2020, nodes_save_path=\"2020_nodes.pkl\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.tools import QueryEngineTool, ToolMetadata\n",
|
||||
"from llama_index.core.query_engine import SubQuestionQueryEngine\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# setup base query engine as tool\n",
|
||||
"query_engine_tools = [\n",
|
||||
" QueryEngineTool(\n",
|
||||
" query_engine=query_engine_2021,\n",
|
||||
" metadata=ToolMetadata(\n",
|
||||
" name=\"apple_2021_10k\",\n",
|
||||
" description=(\"Provides information about Apple financials for year 2021\"),\n",
|
||||
" ),\n",
|
||||
" ),\n",
|
||||
" QueryEngineTool(\n",
|
||||
" query_engine=query_engine_2020,\n",
|
||||
" metadata=ToolMetadata(\n",
|
||||
" name=\"apple_2020_10k\",\n",
|
||||
" description=(\"Provides information about Apple financials for year 2020\"),\n",
|
||||
" ),\n",
|
||||
" ),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"sub_query_engine = SubQuestionQueryEngine.from_defaults(\n",
|
||||
" query_engine_tools=query_engine_tools,\n",
|
||||
" llm=llm,\n",
|
||||
" use_async=True,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Try out Some Comparisons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generated 4 sub questions.\n",
|
||||
"\u001b[1;3;38;2;237;90;200m[apple_2021_10k] Q: What are the deferred assets in 2021?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2021_10k] Q: What are the deferred liabilities in 2021?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;11;159;203m[apple_2020_10k] Q: What are the deferred assets in 2020?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;155;135;227m[apple_2020_10k] Q: What are the deferred liabilities in 2020?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2021_10k] A: $7,200\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;155;135;227m[apple_2020_10k] A: $10,138\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;237;90;200m[apple_2021_10k] A: $25,176 million\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;11;159;203m[apple_2020_10k] A: $19,336\n",
|
||||
"\u001b[0m"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = sub_query_engine.query(\n",
|
||||
" \"Can you compare and contrast the deferred assets and liabilities in 2021 with 2020?\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"In 2021, the deferred assets increased by $5,840 million compared to 2020, while the deferred liabilities decreased by $2,938 million in the same period.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(str(response))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generated 2 sub questions.\n",
|
||||
"\u001b[1;3;38;2;237;90;200m[apple_2021_10k] Q: What is the total number of RSUs in Apple's 2021 financials?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2020_10k] Q: What is the total number of RSUs in Apple's 2020 financials?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;237;90;200m[apple_2021_10k] A: The total number of RSUs in Apple's 2021 financials is 240,427.\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2020_10k] A: The total number of RSUs in Apple's 2020 financials is 310,778.\n",
|
||||
"\u001b[0m"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = sub_query_engine.query(\n",
|
||||
" \"Can you compare and contrast the total number of RSUs in 2021 and 2020?\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generated 2 sub questions.\n",
|
||||
"\u001b[1;3;38;2;237;90;200m[apple_2021_10k] Q: What are the risk factors mentioned in the 2021 financial report of Apple?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2020_10k] Q: What are the risk factors mentioned in the 2020 financial report of Apple?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;237;90;200m[apple_2021_10k] A: The risk factors mentioned in the 2021 financial report of Apple include risks related to COVID-19, macroeconomic and industry risks, political events, trade and international disputes, natural disasters, public health issues, industrial accidents, credit risk, fluctuations in foreign currency exchange rates, changes in tax rates and legislation, volatility in the price of the company's stock, and exposure to legal proceedings and claims.\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2020_10k] A: The risk factors mentioned in the 2020 financial report of Apple include the impact of the COVID-19 pandemic on the company's business operations, financial condition, and stock price; global and regional economic conditions affecting demand for products and services; competition in global markets with rapid technological changes; potential disruptions in the supply chain due to industrial accidents or public health issues; information technology system failures or network disruptions affecting business operations; risks associated with confidential information security and potential unauthorized access; fluctuations in quarterly net sales and operating results due to various factors; stock price volatility impacting investor confidence and employee retention; financial performance risks related to changes in foreign currency exchange rates affecting sales and earnings.\n",
|
||||
"\u001b[0m"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = sub_query_engine.query(\n",
|
||||
" \"Can you compare and contrast the risk factors in 2021 vs. 2020?\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The risk factors mentioned in the 2021 financial report of Apple include risks related to COVID-19, macroeconomic and industry risks, political events, trade and international disputes, natural disasters, public health issues, industrial accidents, credit risk, fluctuations in foreign currency exchange rates, changes in tax rates and legislation, volatility in the price of the company's stock, and exposure to legal proceedings and claims. In contrast, the risk factors mentioned in the 2020 financial report of Apple focused more on the impact of the COVID-19 pandemic on the company's business operations, financial condition, and stock price; global and regional economic conditions affecting demand for products and services; competition in global markets with rapid technological changes; potential disruptions in the supply chain due to industrial accidents or public health issues; information technology system failures or network disruptions affecting business operations; risks associated with confidential information security and potential unauthorized access; fluctuations in quarterly net sales and operating results due to various factors; stock price volatility impacting investor confidence and employee retention; financial performance risks related to changes in foreign currency exchange rates affecting sales and earnings.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(str(response))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "llama_parse",
|
||||
"language": "python",
|
||||
"name": "llama_parse"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Table Extraction with LlamaParse\n",
|
||||
"\n",
|
||||
"This notebook will show you how to extract tables and save them as CSV files thanks to LlamaParse advanced parsing capabilities."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**1. Install needed dependencies**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"! pip install llama-cloud-services pandas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**2. Set you LLAMA_CLOUD_API_KEY as env variable**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"LLAMA_CLOUD_API_KEY: ··········\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"from getpass import getpass\n",
|
||||
"\n",
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = getpass(\"LLAMA_CLOUD_API_KEY: \")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**3. Initialiaze the parser**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(result_type=\"markdown\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**4. Get data**\n",
|
||||
"\n",
|
||||
"This is a PDF with _lots_ of tables!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--2025-07-16 16:20:41-- https://assets.accessible-digital-documents.com/uploads/2017/01/sample-tables.pdf\n",
|
||||
"Resolving assets.accessible-digital-documents.com (assets.accessible-digital-documents.com)... 3.166.135.2, 3.166.135.62, 3.166.135.51, ...\n",
|
||||
"Connecting to assets.accessible-digital-documents.com (assets.accessible-digital-documents.com)|3.166.135.2|:443... connected.\n",
|
||||
"HTTP request sent, awaiting response... 200 OK\n",
|
||||
"Length: 145494 (142K) [application/pdf]\n",
|
||||
"Saving to: ‘sample-tables.pdf’\n",
|
||||
"\n",
|
||||
"sample-tables.pdf 100%[===================>] 142.08K --.-KB/s in 0.04s \n",
|
||||
"\n",
|
||||
"2025-07-16 16:20:41 (3.72 MB/s) - ‘sample-tables.pdf’ saved [145494/145494]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"! wget https://assets.accessible-digital-documents.com/uploads/2017/01/sample-tables.pdf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**5. Parse document**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id b53949f7-9017-4b6a-b30c-be6227271ed2\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"json_result = parser.get_json_result(\"sample-tables.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**6. Get tables!**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"tables = parser.get_tables(json_result, \"tables/\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**7. Load tables**\n",
|
||||
"\n",
|
||||
"Let's show one example table!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"application/vnd.google.colaboratory.intrinsic+json": {
|
||||
"summary": "{\n \"name\": \"display(df\",\n \"rows\": 8,\n \"fields\": [\n {\n \"column\": \"Rainfall\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"Average\",\n \"\",\n \"24 hour high\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Americas\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 908,\n \"min\": 9,\n \"max\": 2010,\n \"num_unique_values\": 8,\n \"samples\": [\n 104,\n 133,\n 2010\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Asia\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 7,\n \"samples\": [\n \"\",\n 201.0,\n 28.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Europe\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 7,\n \"samples\": [\n \"\",\n 193.0,\n 29.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"Africa\",\n \"properties\": {\n \"dtype\": \"object\",\n \"num_unique_values\": 7,\n \"samples\": [\n \"\",\n 144.0,\n 20.0\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}",
|
||||
"type": "dataframe"
|
||||
},
|
||||
"text/html": [
|
||||
"\n",
|
||||
" <div id=\"df-94a74c8f-1062-4a80-8d3f-32f0fbadf7bb\" class=\"colab-df-container\">\n",
|
||||
" <div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>Rainfall</th>\n",
|
||||
" <th>Americas</th>\n",
|
||||
" <th>Asia</th>\n",
|
||||
" <th>Europe</th>\n",
|
||||
" <th>Africa</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>0</th>\n",
|
||||
" <td>(inches)</td>\n",
|
||||
" <td>2010</td>\n",
|
||||
" <td></td>\n",
|
||||
" <td></td>\n",
|
||||
" <td></td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>1</th>\n",
|
||||
" <td>Average</td>\n",
|
||||
" <td>104</td>\n",
|
||||
" <td>201.0</td>\n",
|
||||
" <td>193.0</td>\n",
|
||||
" <td>144.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2</th>\n",
|
||||
" <td>24 hour high</td>\n",
|
||||
" <td>15</td>\n",
|
||||
" <td>26.0</td>\n",
|
||||
" <td>27.0</td>\n",
|
||||
" <td>18.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>3</th>\n",
|
||||
" <td>12 hour high</td>\n",
|
||||
" <td>9</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>11.0</td>\n",
|
||||
" <td>12.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>4</th>\n",
|
||||
" <td></td>\n",
|
||||
" <td>2009</td>\n",
|
||||
" <td></td>\n",
|
||||
" <td></td>\n",
|
||||
" <td></td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>5</th>\n",
|
||||
" <td>Average</td>\n",
|
||||
" <td>133</td>\n",
|
||||
" <td>244.0</td>\n",
|
||||
" <td>155.0</td>\n",
|
||||
" <td>166.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>6</th>\n",
|
||||
" <td>24 hour high</td>\n",
|
||||
" <td>27</td>\n",
|
||||
" <td>28.0</td>\n",
|
||||
" <td>29.0</td>\n",
|
||||
" <td>20.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>7</th>\n",
|
||||
" <td>12 hour high</td>\n",
|
||||
" <td>11</td>\n",
|
||||
" <td>12.0</td>\n",
|
||||
" <td>13.0</td>\n",
|
||||
" <td>16.0</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>\n",
|
||||
" <div class=\"colab-df-buttons\">\n",
|
||||
"\n",
|
||||
" <div class=\"colab-df-container\">\n",
|
||||
" <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-94a74c8f-1062-4a80-8d3f-32f0fbadf7bb')\"\n",
|
||||
" title=\"Convert this dataframe to an interactive table.\"\n",
|
||||
" style=\"display:none;\">\n",
|
||||
"\n",
|
||||
" <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
|
||||
" <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
|
||||
" </svg>\n",
|
||||
" </button>\n",
|
||||
"\n",
|
||||
" <style>\n",
|
||||
" .colab-df-container {\n",
|
||||
" display:flex;\n",
|
||||
" gap: 12px;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .colab-df-convert {\n",
|
||||
" background-color: #E8F0FE;\n",
|
||||
" border: none;\n",
|
||||
" border-radius: 50%;\n",
|
||||
" cursor: pointer;\n",
|
||||
" display: none;\n",
|
||||
" fill: #1967D2;\n",
|
||||
" height: 32px;\n",
|
||||
" padding: 0 0 0 0;\n",
|
||||
" width: 32px;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .colab-df-convert:hover {\n",
|
||||
" background-color: #E2EBFA;\n",
|
||||
" box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
|
||||
" fill: #174EA6;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .colab-df-buttons div {\n",
|
||||
" margin-bottom: 4px;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" [theme=dark] .colab-df-convert {\n",
|
||||
" background-color: #3B4455;\n",
|
||||
" fill: #D2E3FC;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" [theme=dark] .colab-df-convert:hover {\n",
|
||||
" background-color: #434B5C;\n",
|
||||
" box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
|
||||
" filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
|
||||
" fill: #FFFFFF;\n",
|
||||
" }\n",
|
||||
" </style>\n",
|
||||
"\n",
|
||||
" <script>\n",
|
||||
" const buttonEl =\n",
|
||||
" document.querySelector('#df-94a74c8f-1062-4a80-8d3f-32f0fbadf7bb button.colab-df-convert');\n",
|
||||
" buttonEl.style.display =\n",
|
||||
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
|
||||
"\n",
|
||||
" async function convertToInteractive(key) {\n",
|
||||
" const element = document.querySelector('#df-94a74c8f-1062-4a80-8d3f-32f0fbadf7bb');\n",
|
||||
" const dataTable =\n",
|
||||
" await google.colab.kernel.invokeFunction('convertToInteractive',\n",
|
||||
" [key], {});\n",
|
||||
" if (!dataTable) return;\n",
|
||||
"\n",
|
||||
" const docLinkHtml = 'Like what you see? Visit the ' +\n",
|
||||
" '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
|
||||
" + ' to learn more about interactive tables.';\n",
|
||||
" element.innerHTML = '';\n",
|
||||
" dataTable['output_type'] = 'display_data';\n",
|
||||
" await google.colab.output.renderOutput(dataTable, element);\n",
|
||||
" const docLink = document.createElement('div');\n",
|
||||
" docLink.innerHTML = docLinkHtml;\n",
|
||||
" element.appendChild(docLink);\n",
|
||||
" }\n",
|
||||
" </script>\n",
|
||||
" </div>\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" <div id=\"df-54b2aa43-838b-47d3-9209-2fb18153cf87\">\n",
|
||||
" <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-54b2aa43-838b-47d3-9209-2fb18153cf87')\"\n",
|
||||
" title=\"Suggest charts\"\n",
|
||||
" style=\"display:none;\">\n",
|
||||
"\n",
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
|
||||
" width=\"24px\">\n",
|
||||
" <g>\n",
|
||||
" <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
|
||||
" </g>\n",
|
||||
"</svg>\n",
|
||||
" </button>\n",
|
||||
"\n",
|
||||
"<style>\n",
|
||||
" .colab-df-quickchart {\n",
|
||||
" --bg-color: #E8F0FE;\n",
|
||||
" --fill-color: #1967D2;\n",
|
||||
" --hover-bg-color: #E2EBFA;\n",
|
||||
" --hover-fill-color: #174EA6;\n",
|
||||
" --disabled-fill-color: #AAA;\n",
|
||||
" --disabled-bg-color: #DDD;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" [theme=dark] .colab-df-quickchart {\n",
|
||||
" --bg-color: #3B4455;\n",
|
||||
" --fill-color: #D2E3FC;\n",
|
||||
" --hover-bg-color: #434B5C;\n",
|
||||
" --hover-fill-color: #FFFFFF;\n",
|
||||
" --disabled-bg-color: #3B4455;\n",
|
||||
" --disabled-fill-color: #666;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .colab-df-quickchart {\n",
|
||||
" background-color: var(--bg-color);\n",
|
||||
" border: none;\n",
|
||||
" border-radius: 50%;\n",
|
||||
" cursor: pointer;\n",
|
||||
" display: none;\n",
|
||||
" fill: var(--fill-color);\n",
|
||||
" height: 32px;\n",
|
||||
" padding: 0;\n",
|
||||
" width: 32px;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .colab-df-quickchart:hover {\n",
|
||||
" background-color: var(--hover-bg-color);\n",
|
||||
" box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
|
||||
" fill: var(--button-hover-fill-color);\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .colab-df-quickchart-complete:disabled,\n",
|
||||
" .colab-df-quickchart-complete:disabled:hover {\n",
|
||||
" background-color: var(--disabled-bg-color);\n",
|
||||
" fill: var(--disabled-fill-color);\n",
|
||||
" box-shadow: none;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .colab-df-spinner {\n",
|
||||
" border: 2px solid var(--fill-color);\n",
|
||||
" border-color: transparent;\n",
|
||||
" border-bottom-color: var(--fill-color);\n",
|
||||
" animation:\n",
|
||||
" spin 1s steps(1) infinite;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" @keyframes spin {\n",
|
||||
" 0% {\n",
|
||||
" border-color: transparent;\n",
|
||||
" border-bottom-color: var(--fill-color);\n",
|
||||
" border-left-color: var(--fill-color);\n",
|
||||
" }\n",
|
||||
" 20% {\n",
|
||||
" border-color: transparent;\n",
|
||||
" border-left-color: var(--fill-color);\n",
|
||||
" border-top-color: var(--fill-color);\n",
|
||||
" }\n",
|
||||
" 30% {\n",
|
||||
" border-color: transparent;\n",
|
||||
" border-left-color: var(--fill-color);\n",
|
||||
" border-top-color: var(--fill-color);\n",
|
||||
" border-right-color: var(--fill-color);\n",
|
||||
" }\n",
|
||||
" 40% {\n",
|
||||
" border-color: transparent;\n",
|
||||
" border-right-color: var(--fill-color);\n",
|
||||
" border-top-color: var(--fill-color);\n",
|
||||
" }\n",
|
||||
" 60% {\n",
|
||||
" border-color: transparent;\n",
|
||||
" border-right-color: var(--fill-color);\n",
|
||||
" }\n",
|
||||
" 80% {\n",
|
||||
" border-color: transparent;\n",
|
||||
" border-right-color: var(--fill-color);\n",
|
||||
" border-bottom-color: var(--fill-color);\n",
|
||||
" }\n",
|
||||
" 90% {\n",
|
||||
" border-color: transparent;\n",
|
||||
" border-bottom-color: var(--fill-color);\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"\n",
|
||||
" <script>\n",
|
||||
" async function quickchart(key) {\n",
|
||||
" const quickchartButtonEl =\n",
|
||||
" document.querySelector('#' + key + ' button');\n",
|
||||
" quickchartButtonEl.disabled = true; // To prevent multiple clicks.\n",
|
||||
" quickchartButtonEl.classList.add('colab-df-spinner');\n",
|
||||
" try {\n",
|
||||
" const charts = await google.colab.kernel.invokeFunction(\n",
|
||||
" 'suggestCharts', [key], {});\n",
|
||||
" } catch (error) {\n",
|
||||
" console.error('Error during call to suggestCharts:', error);\n",
|
||||
" }\n",
|
||||
" quickchartButtonEl.classList.remove('colab-df-spinner');\n",
|
||||
" quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
|
||||
" }\n",
|
||||
" (() => {\n",
|
||||
" let quickchartButtonEl =\n",
|
||||
" document.querySelector('#df-54b2aa43-838b-47d3-9209-2fb18153cf87 button');\n",
|
||||
" quickchartButtonEl.style.display =\n",
|
||||
" google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
|
||||
" })();\n",
|
||||
" </script>\n",
|
||||
" </div>\n",
|
||||
"\n",
|
||||
" </div>\n",
|
||||
" </div>\n"
|
||||
],
|
||||
"text/plain": [
|
||||
" Rainfall Americas Asia Europe Africa\n",
|
||||
"0 (inches) 2010 \n",
|
||||
"1 Average 104 201.0 193.0 144.0\n",
|
||||
"2 24 hour high 15 26.0 27.0 18.0\n",
|
||||
"3 12 hour high 9 10.0 11.0 12.0\n",
|
||||
"4 2009 \n",
|
||||
"5 Average 133 244.0 155.0 166.0\n",
|
||||
"6 24 hour high 27 28.0 29.0 20.0\n",
|
||||
"7 12 hour high 11 12.0 13.0 16.0"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"from IPython.display import display\n",
|
||||
"\n",
|
||||
"df = pd.read_csv(\n",
|
||||
" \"/content/tables/table_2025_16_07_16_30_01_569.csv\",\n",
|
||||
")\n",
|
||||
"display(df.fillna(\"\"))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"provenance": []
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -147,7 +147,7 @@
|
||||
"documents = []\n",
|
||||
"\n",
|
||||
"for i, page in enumerate(pages):\n",
|
||||
" # loop trough items of the page\n",
|
||||
" # loop through items of the page\n",
|
||||
" for item in page[\"items\"]:\n",
|
||||
" document = Document(\n",
|
||||
" text=item[\"md\"], extra_info={\"bbox\": item[\"bBox\"], \"page\": i}\n",
|
||||
|
||||
+243
-23
@@ -1,15 +1,41 @@
|
||||
# LlamaExtract
|
||||
|
||||
LlamaExtract provides a simple API for extracting structured data from unstructured documents like PDFs, text files and images (upcoming).
|
||||
LlamaExtract provides a simple API for extracting structured data from unstructured documents like PDFs, text files and images.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Quick Start](#quick-start)
|
||||
- [Supported File Types](#supported-file-types)
|
||||
- [Different Input Types](#different-input-types)
|
||||
- [Async Extraction](#async-extraction)
|
||||
- [Core Concepts](#core-concepts)
|
||||
- [Defining Schemas](#defining-schemas)
|
||||
- [Using Pydantic (Recommended)](#using-pydantic-recommended)
|
||||
- [Using JSON Schema](#using-json-schema)
|
||||
- [Important restrictions on JSON/Pydantic Schema](#important-restrictions-on-jsonpydantic-schema)
|
||||
- [Extraction Configuration](#extraction-configuration)
|
||||
- [Configuration Options](#configuration-options)
|
||||
- [Extraction Agents (Advanced)](#extraction-agents-advanced)
|
||||
- [Creating Agents](#creating-agents)
|
||||
- [Agent Batch Processing](#agent-batch-processing)
|
||||
- [Updating Agent Schemas](#updating-agent-schemas)
|
||||
- [Managing Agents](#managing-agents)
|
||||
- [When to Use Agents vs Direct Extraction](#when-to-use-agents-vs-direct-extraction)
|
||||
- [Installation](#installation)
|
||||
- [Tips & Best Practices](#tips--best-practices)
|
||||
- [Additional Resources](#additional-resources)
|
||||
|
||||
## Quick Start
|
||||
|
||||
The simplest way to get started is to use the stateless API with the extraction configuration and the file/text to extract from:
|
||||
|
||||
```python
|
||||
from llama_cloud_services import LlamaExtract
|
||||
from llama_cloud import ExtractConfig, ExtractMode
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Initialize client
|
||||
extractor = LlamaExtract()
|
||||
extractor = LlamaExtract(api_key="YOUR_API_KEY")
|
||||
|
||||
|
||||
# Define schema using Pydantic
|
||||
@@ -19,29 +45,97 @@ class Resume(BaseModel):
|
||||
skills: list[str] = Field(description="Technical skills and technologies")
|
||||
|
||||
|
||||
# Create extraction agent
|
||||
agent = extractor.create_agent(name="resume-parser", data_schema=Resume)
|
||||
# Configure extraction settings
|
||||
config = ExtractConfig(extraction_mode=ExtractMode.FAST)
|
||||
|
||||
# Extract data from document
|
||||
result = agent.extract("resume.pdf")
|
||||
# Extract data directly from document - no agent needed!
|
||||
result = extractor.extract(Resume, config, "resume.pdf")
|
||||
print(result.data)
|
||||
```
|
||||
|
||||
### Supported File Types
|
||||
|
||||
LlamaExtract supports the following file formats:
|
||||
|
||||
- **Documents**: PDF (.pdf), Word (.docx)
|
||||
- **Text files**: Plain text (.txt), CSV (.csv), JSON (.json), HTML (.html, .htm), Markdown (.md)
|
||||
- **Images**: PNG (.png), JPEG (.jpg, .jpeg)
|
||||
|
||||
### Different Input Types
|
||||
|
||||
```python
|
||||
# From file path (string or Path)
|
||||
result = extractor.extract(Resume, config, "resume.pdf")
|
||||
|
||||
# From file handle
|
||||
with open("resume.pdf", "rb") as f:
|
||||
result = extractor.extract(Resume, config, f)
|
||||
|
||||
# From bytes with filename
|
||||
with open("resume.pdf", "rb") as f:
|
||||
file_bytes = f.read()
|
||||
from llama_cloud_services.extract import SourceText
|
||||
|
||||
result = extractor.extract(
|
||||
Resume, config, SourceText(file=file_bytes, filename="resume.pdf")
|
||||
)
|
||||
|
||||
# From text content
|
||||
text = "Name: John Doe\nEmail: john@example.com\nSkills: Python, AI"
|
||||
result = extractor.extract(Resume, config, SourceText(text_content=text))
|
||||
```
|
||||
|
||||
### Async Extraction
|
||||
|
||||
For better performance with multiple files or when integrating with async applications.
|
||||
Here `queue_extraction` will enqueue the extraction jobs and exit. Alternatively, you
|
||||
can use `aextract` to poll for the job and return the extraction results.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
|
||||
async def extract_resumes():
|
||||
# Async extraction
|
||||
result = await extractor.aextract(Resume, config, "resume.pdf")
|
||||
print(result.data)
|
||||
|
||||
# Queue extraction jobs (returns immediately)
|
||||
jobs = await extractor.queue_extraction(
|
||||
Resume, config, ["resume1.pdf", "resume2.pdf"]
|
||||
)
|
||||
print(f"Queued {len(jobs)} extraction jobs")
|
||||
return jobs
|
||||
|
||||
|
||||
# Run async function
|
||||
jobs = asyncio.run(extract_resumes())
|
||||
# Check job status
|
||||
for job in jobs:
|
||||
status = agent.get_extraction_job(job.id).status
|
||||
print(f"Job {job.id}: {status}")
|
||||
|
||||
# Get results when complete
|
||||
results = [agent.get_extraction_run_for_job(job.id) for job in jobs]
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
- **Extraction Agents**: Reusable extractors configured with a specific schema and extraction settings.
|
||||
- **Data Schema**: Structure definition for the data you want to extract in the form of a JSON schema or a Pydantic model.
|
||||
- **Extraction Config**: Settings that control how extraction is performed (e.g., speed vs accuracy trade-offs).
|
||||
- **Extraction Jobs**: Asynchronous extraction tasks that can be monitored.
|
||||
- **Extraction Agents** (Advanced): Reusable extractors configured with a specific schema and extraction settings.
|
||||
|
||||
## Defining Schemas
|
||||
|
||||
Schemas can be defined using either Pydantic models or JSON Schema:
|
||||
Schemas define the structure of data you want to extract. You can use either Pydantic models or JSON Schema:
|
||||
|
||||
### Using Pydantic (Recommended)
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional
|
||||
from llama_cloud import ExtractConfig, ExtractMode
|
||||
|
||||
|
||||
class Experience(BaseModel):
|
||||
@@ -54,6 +148,11 @@ class Experience(BaseModel):
|
||||
class Resume(BaseModel):
|
||||
name: str = Field(description="Candidate name")
|
||||
experience: List[Experience] = Field(description="Work history")
|
||||
|
||||
|
||||
# Use the schema for extraction
|
||||
config = ExtractConfig(extraction_mode=ExtractMode.FAST)
|
||||
result = extractor.extract(Resume, config, "resume.pdf")
|
||||
```
|
||||
|
||||
### Using JSON Schema
|
||||
@@ -88,7 +187,9 @@ schema = {
|
||||
},
|
||||
}
|
||||
|
||||
agent = extractor.create_agent(name="resume-parser", data_schema=schema)
|
||||
# Use the schema for extraction
|
||||
config = ExtractConfig(extraction_mode=ExtractMode.FAST)
|
||||
result = extractor.extract(schema, config, "resume.pdf")
|
||||
```
|
||||
|
||||
### Important restrictions on JSON/Pydantic Schema
|
||||
@@ -97,21 +198,111 @@ _LlamaExtract only supports a subset of the JSON Schema specification._ While li
|
||||
be sufficient for a wide variety of use-cases.
|
||||
|
||||
- All fields are required by default. Nullable fields must be explicitly marked as such,
|
||||
using `"anyOf"` with a `"null"` type. See `"start_date"` field above.
|
||||
- Root node must be of type `"object"`.
|
||||
using `anyOf` with a `null` type. See `"start_date"` field above.
|
||||
- Root node must be of type `object`.
|
||||
- Schema nesting must be limited to within 5 levels.
|
||||
- The important fields are key names/titles, type and description. Fields for
|
||||
formatting, default values, etc. are not supported.
|
||||
formatting, default values, etc. are **not supported**. If you need these, you can add the
|
||||
restrictions to your field description and/or use a post-processing step. e.g. default values can be supported by making a field optional and then setting `"null"` values from the extraction result to the default value.
|
||||
- There are other restrictions on number of keys, size of the schema, etc. that you may
|
||||
hit for complex extraction use cases. In such cases, it is worth thinking how to restructure
|
||||
your extraction workflow to fit within these constraints, e.g. by extracting subset of fields
|
||||
and later merging them together.
|
||||
|
||||
## Other Extraction APIs
|
||||
## Extraction Configuration
|
||||
|
||||
### Batch Processing
|
||||
Configure how extraction is performed using `ExtractConfig`. The schema is the most important part, but several configuration options can significantly impact the extraction process.
|
||||
|
||||
Process multiple files asynchronously:
|
||||
```python
|
||||
from llama_cloud import ExtractConfig, ExtractMode, ChunkMode, ExtractTarget
|
||||
|
||||
# Basic configuration
|
||||
config = ExtractConfig(
|
||||
extraction_mode=ExtractMode.BALANCED, # FAST, BALANCED, MULTIMODAL, PREMIUM
|
||||
extraction_target=ExtractTarget.PER_DOC, # PER_DOC, PER_PAGE
|
||||
system_prompt="Focus on the most recent data",
|
||||
page_range="1-5,10-15", # Extract from specific pages
|
||||
)
|
||||
|
||||
# Advanced configuration
|
||||
advanced_config = ExtractConfig(
|
||||
extraction_mode=ExtractMode.MULTIMODAL,
|
||||
chunk_mode=ChunkMode.PAGE, # PAGE, SECTION
|
||||
high_resolution_mode=True, # Better OCR accuracy
|
||||
invalidate_cache=False, # Bypass cached results
|
||||
cite_sources=True, # Enable source citations
|
||||
use_reasoning=True, # Enable reasoning (not in FAST mode)
|
||||
confidence_scores=True, # MULTIMODAL/PREMIUM only
|
||||
)
|
||||
```
|
||||
|
||||
### Key Configuration Options
|
||||
|
||||
**Extraction Mode**: Controls processing quality and speed
|
||||
|
||||
- `FAST`: Fastest processing, suitable for simple documents with no OCR
|
||||
- `BALANCED`: Good speed/accuracy tradeoff for text-rich documents
|
||||
- `MULTIMODAL`: For visually rich documents with text, tables, and images (recommended)
|
||||
- `PREMIUM`: Highest accuracy with OCR, complex table/header detection
|
||||
|
||||
**Extraction Target**: Defines extraction scope
|
||||
|
||||
- `PER_DOC`: Apply schema to entire document (default)
|
||||
- `PER_PAGE`: Apply schema to each page, returns array of results
|
||||
|
||||
**Advanced Options**:
|
||||
|
||||
- `system_prompt`: Additional system-level instructions
|
||||
- `page_range`: Specific pages to extract (e.g., "1,3,5-7,9")
|
||||
- `chunk_mode`: Document splitting strategy (`PAGE` or `SECTION`)
|
||||
- `high_resolution_mode`: Better OCR for small text (slower processing)
|
||||
|
||||
**Extensions** (return additional metadata):
|
||||
|
||||
- `cite_sources`: Source tracing for extracted fields
|
||||
- `use_reasoning`: Explanations for extraction decisions
|
||||
- `confidence_scores`: Quantitative confidence measures (MULTIMODAL/PREMIUM only)
|
||||
|
||||
For complete configuration options, advanced settings, and detailed examples, see the [LlamaExtract Configuration Documentation](https://docs.cloud.llamaindex.ai/llamaextract/features/options).
|
||||
|
||||
## Extraction Agents (Advanced)
|
||||
|
||||
For reusable extraction workflows, you can create extraction agents that encapsulate both schema and configuration:
|
||||
|
||||
### Creating Agents
|
||||
|
||||
```python
|
||||
from llama_cloud_services import LlamaExtract
|
||||
from llama_cloud import ExtractConfig, ExtractMode
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Initialize client
|
||||
extractor = LlamaExtract()
|
||||
|
||||
|
||||
# Define schema
|
||||
class Resume(BaseModel):
|
||||
name: str = Field(description="Full name of candidate")
|
||||
email: str = Field(description="Email address")
|
||||
skills: list[str] = Field(description="Technical skills and technologies")
|
||||
|
||||
|
||||
# Configure extraction settings
|
||||
config = ExtractConfig(extraction_mode=ExtractMode.FAST)
|
||||
|
||||
# Create extraction agent
|
||||
agent = extractor.create_agent(
|
||||
name="resume-parser", data_schema=Resume, config=config
|
||||
)
|
||||
|
||||
# Use the agent
|
||||
result = agent.extract("resume.pdf")
|
||||
print(result.data)
|
||||
```
|
||||
|
||||
### Agent Batch Processing
|
||||
|
||||
Process multiple files with an agent:
|
||||
|
||||
```python
|
||||
# Queue multiple files for extraction
|
||||
@@ -126,7 +317,7 @@ for job in jobs:
|
||||
results = [agent.get_extraction_run_for_job(job.id) for job in jobs]
|
||||
```
|
||||
|
||||
### Updating Schemas
|
||||
### Updating Agent Schemas
|
||||
|
||||
Schemas can be modified and updated after creation:
|
||||
|
||||
@@ -151,33 +342,62 @@ agent = extractor.get_agent(name="resume-parser")
|
||||
extractor.delete_agent(agent.id)
|
||||
```
|
||||
|
||||
### When to Use Agents vs Direct Extraction
|
||||
|
||||
**Use Direct Extraction When:**
|
||||
|
||||
- One-off extractions
|
||||
- Different schemas for different documents
|
||||
- Simple workflows
|
||||
- Getting started quickly
|
||||
|
||||
**Use Extraction Agents When:**
|
||||
|
||||
- Repeated extractions with the same schema
|
||||
- Team collaboration (shared, named extractors)
|
||||
- Complex workflows requiring state management
|
||||
- Production systems with consistent extraction patterns
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install llama-extract==0.1.0
|
||||
pip install llama-cloud-services
|
||||
```
|
||||
|
||||
## Tips & Best Practices
|
||||
|
||||
At the core of LlamaExtract is the schema, which defines the structure of the data you want to extract from your documents.
|
||||
|
||||
1. **Schema Design**:
|
||||
|
||||
- Try to limit schema nesting to 3-4 levels.
|
||||
- Make fields optional when data might not always be present. Having required fields may force the model
|
||||
to hallucinate when these fields are not present in the documents.
|
||||
- When you want to extract a variable number of entities, use an `array` type. Note that you cannot use
|
||||
- When you want to extract a variable number of entities, use an `array` type. However, note that you cannot use
|
||||
an `array` type for the root node.
|
||||
- Use descriptive field names and detailed descriptions. Use descriptions to pass formatting
|
||||
instructions or few-shot examples.
|
||||
- Start simple and iteratively build your schema to incorporate requirements.
|
||||
- Above all, start simple and iteratively build your schema to incorporate requirements.
|
||||
|
||||
2. **Running Extractions**:
|
||||
- Note that resetting `agent.schema` will not save the schema to the database,
|
||||
until you call `agent.save`, but it will be used for running extractions.
|
||||
- Check job status prior to accessing results. Any extraction error should be available as
|
||||
part of `job.error` or `extraction_run.error` fields for debugging.
|
||||
- Consider async operations (`queue_extraction`) for large-scale extraction once you have finalized your schema.
|
||||
- Check extraction results for any errors. Error information is available in the `result.error` field for debugging.
|
||||
- Consider async operations (`aextract` or `queue_extraction`) for large-scale extraction or when processing multiple files.
|
||||
- For repeated extractions with the same schema, consider creating an extraction agent to avoid redefining the schema each time.
|
||||
|
||||
### Hitting "The response was too long to be processed" Error
|
||||
|
||||
This implies that the extraction response is hitting output token limits of the LLM. In such cases, it is worth rethinking the design of your schema to enable a more efficient/scalable extraction. e.g.
|
||||
|
||||
- Instead of one field that extracts a complex object, you can use multiple fields to distribute the extraction logic.
|
||||
- You can also use multiple schemas to extract different subsets of fields from the same document and merge them later.
|
||||
|
||||
Another option (orthogonal to the above) is to break the document into smaller sections and extract from each section individually, when possible. LlamaExtract will in most cases be able to handle both document and schema chunking automatically, but there are cases where you may need to do this manually.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Example Notebook](examples/resume_screening.ipynb) - Detailed walkthrough of resume parsing
|
||||
- [Extract Documentation](https://docs.cloud.llamaindex.ai/llamaextract/getting_started) - Details on Extract features, API and examples.
|
||||
- [Example Notebook](docs/examples-py/extract/resume_screening.ipynb) - Detailed walkthrough of resume parsing
|
||||
- [Example Application with TypeScript](./examples-ts/extract/) - End-to-end examples using LlamaExtract TypeScript client.
|
||||
- [Discord Community](https://discord.com/invite/eN6D2HQ4aX) - Get help and share feedback
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# LlamaCloud Index + Retriever
|
||||
|
||||
LlamaCloud is a new generation of managed parsing, ingestion, and retrieval services, designed to bring production-grade context-augmentation to your LLM and RAG applications.
|
||||
|
||||
Currently, LlamaCloud supports
|
||||
|
||||
- Managed Ingestion API, handling parsing and document management
|
||||
- Managed Retrieval API, configuring optimal retrieval for your RAG system
|
||||
|
||||
## Access
|
||||
|
||||
We are opening up a private beta to a limited set of enterprise partners for the managed ingestion and retrieval API. If you’re interested in centralizing your data pipelines and spending more time working on your actual RAG use cases, come [talk to us.](https://www.llamaindex.ai/contact)
|
||||
|
||||
If you have access to LlamaCloud, you can visit [LlamaCloud](https://cloud.llamaindex.ai) to sign in and get an API key.
|
||||
|
||||
## Setup
|
||||
|
||||
First, make sure you have the latest LlamaIndex version installed.
|
||||
|
||||
```
|
||||
pip uninstall llama-index # run this if upgrading from v0.9.x or older
|
||||
pip install -U llama-index --upgrade --no-cache-dir --force-reinstall
|
||||
```
|
||||
|
||||
The `llama-index-indices-managed-llama-cloud` package is included with the above install, but you can also install directly
|
||||
|
||||
```
|
||||
pip install -U llama-index-indices-managed-llama-cloud
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
You can create an index on LlamaCloud using the following code. By default, new indexes use managed embeddings (OpenAI text-embedding-3-small, 1536 dimensions, 1 credit/page):
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
os.environ[
|
||||
"LLAMA_CLOUD_API_KEY"
|
||||
] = "llx-..." # can provide API-key in env or in the constructor later on
|
||||
|
||||
from llama_index.core import SimpleDirectoryReader
|
||||
from llama_cloud_services import LlamaCloudIndex
|
||||
|
||||
# create a new index (uses managed embeddings by default)
|
||||
index = LlamaCloudIndex.from_documents(
|
||||
documents,
|
||||
"my_first_index",
|
||||
project_name="default",
|
||||
api_key="llx-...",
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
# connect to an existing index
|
||||
index = LlamaCloudIndex("my_first_index", project_name="default")
|
||||
```
|
||||
|
||||
You can also configure a retriever for managed retrieval:
|
||||
|
||||
```python
|
||||
# from the existing index
|
||||
index.as_retriever()
|
||||
|
||||
# from scratch
|
||||
from llama_index.indices.managed.llama_cloud import LlamaCloudRetriever
|
||||
|
||||
retriever = LlamaCloudRetriever("my_first_index", project_name="default")
|
||||
```
|
||||
|
||||
And of course, you can use other index shortcuts to get use out of your new managed index:
|
||||
|
||||
```python
|
||||
query_engine = index.as_query_engine(llm=llm)
|
||||
|
||||
chat_engine = index.as_chat_engine(llm=llm)
|
||||
```
|
||||
|
||||
## Retriever Settings
|
||||
|
||||
A full list of retriever settings/kwargs is below:
|
||||
|
||||
- `dense_similarity_top_k`: Optional[int] -- If greater than 0, retrieve `k` nodes using dense retrieval
|
||||
- `sparse_similarity_top_k`: Optional[int] -- If greater than 0, retrieve `k` nodes using sparse retrieval
|
||||
- `enable_reranking`: Optional[bool] -- Whether to enable reranking or not. Sacrifices some speed for accuracy
|
||||
- `rerank_top_n`: Optional[int] -- The number of nodes to return after reranking initial retrieval results
|
||||
- `alpha` Optional[float] -- The weighting between dense and sparse retrieval. 1 = Full dense retrieval, 0 = Full sparse retrieval.
|
||||
@@ -1,3 +0,0 @@
|
||||
from llama_cloud_services.extract.extract import LlamaExtract, ExtractionAgent
|
||||
|
||||
__all__ = ["LlamaExtract", "ExtractionAgent"]
|
||||
@@ -1,717 +0,0 @@
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
from io import BufferedIOBase, BufferedReader, BytesIO
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Type, Union, Coroutine, Any, TypeVar
|
||||
import warnings
|
||||
import httpx
|
||||
from pydantic import BaseModel
|
||||
from llama_cloud import (
|
||||
ExtractAgent as CloudExtractAgent,
|
||||
ExtractAgentCreate,
|
||||
ExtractConfig,
|
||||
ExtractJob,
|
||||
ExtractJobCreate,
|
||||
ExtractRun,
|
||||
ExtractSchemaValidateRequest,
|
||||
ExtractAgentUpdate,
|
||||
File,
|
||||
ExtractMode,
|
||||
StatusEnum,
|
||||
Project,
|
||||
ExtractTarget,
|
||||
LlamaExtractSettings,
|
||||
PaginatedExtractRunsResponse,
|
||||
)
|
||||
from llama_cloud.client import AsyncLlamaCloud
|
||||
from llama_cloud_services.extract.utils import JSONObjectType, augment_async_errors
|
||||
from llama_index.core.schema import BaseComponent
|
||||
from llama_index.core.async_utils import run_jobs
|
||||
from llama_index.core.bridge.pydantic import Field, PrivateAttr
|
||||
from llama_index.core.constants import DEFAULT_BASE_URL
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
FileInput = Union[str, Path, bytes, BufferedIOBase]
|
||||
SchemaInput = Union[JSONObjectType, Type[BaseModel]]
|
||||
|
||||
DEFAULT_EXTRACT_CONFIG = ExtractConfig(
|
||||
extraction_target=ExtractTarget.PER_DOC,
|
||||
extraction_mode=ExtractMode.BALANCED,
|
||||
)
|
||||
|
||||
|
||||
class ExtractionAgent:
|
||||
"""Class representing a single extraction agent with methods for extraction operations."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: AsyncLlamaCloud,
|
||||
agent: CloudExtractAgent,
|
||||
project_id: Optional[str] = None,
|
||||
organization_id: Optional[str] = None,
|
||||
check_interval: int = 1,
|
||||
max_timeout: int = 2000,
|
||||
num_workers: int = 4,
|
||||
show_progress: bool = True,
|
||||
verbose: bool = False,
|
||||
verify: Optional[bool] = True,
|
||||
httpx_timeout: Optional[float] = 60,
|
||||
):
|
||||
self._client = client
|
||||
self._agent = agent
|
||||
self._project_id = project_id
|
||||
self._organization_id = organization_id
|
||||
self.check_interval = check_interval
|
||||
self.max_timeout = max_timeout
|
||||
self.num_workers = num_workers
|
||||
self.show_progress = show_progress
|
||||
self.verify = verify
|
||||
self.httpx_timeout = httpx_timeout
|
||||
self._verbose = verbose
|
||||
self._data_schema: Union[JSONObjectType, None] = None
|
||||
self._config: Union[ExtractConfig, None] = None
|
||||
self._thread_pool = ThreadPoolExecutor(
|
||||
max_workers=min(10, (os.cpu_count() or 1) + 4)
|
||||
)
|
||||
|
||||
def _run_in_thread(self, coro: Coroutine[Any, Any, T]) -> T:
|
||||
"""Run coroutine in a separate thread to avoid event loop issues"""
|
||||
|
||||
def run_coro() -> T:
|
||||
async def wrapped_coro() -> T:
|
||||
# Get the original client to preserve its configuration
|
||||
original_client = self._client._client_wrapper.httpx_client
|
||||
|
||||
# Create a new client with the same configuration as the original
|
||||
async with httpx.AsyncClient(
|
||||
verify=self.verify,
|
||||
timeout=self.httpx_timeout,
|
||||
) as client:
|
||||
# Temporarily replace the client
|
||||
self._client._client_wrapper.httpx_client = client
|
||||
try:
|
||||
return await coro
|
||||
finally:
|
||||
# Restore the original client
|
||||
self._client._client_wrapper.httpx_client = original_client
|
||||
|
||||
return asyncio.run(wrapped_coro())
|
||||
|
||||
return self._thread_pool.submit(run_coro).result()
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return self._agent.id
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._agent.name
|
||||
|
||||
@property
|
||||
def data_schema(self) -> dict:
|
||||
return self._agent.data_schema if not self._data_schema else self._data_schema
|
||||
|
||||
@data_schema.setter
|
||||
def data_schema(self, data_schema: SchemaInput) -> None:
|
||||
processed_schema: JSONObjectType
|
||||
if isinstance(data_schema, dict):
|
||||
# TODO: if we expose a get_validated JSON schema method, we can use it here
|
||||
processed_schema = data_schema # type: ignore
|
||||
elif isinstance(data_schema, type) and issubclass(data_schema, BaseModel):
|
||||
processed_schema = data_schema.model_json_schema()
|
||||
else:
|
||||
raise ValueError(
|
||||
"data_schema must be either a dictionary or a Pydantic model"
|
||||
)
|
||||
validated_schema = self._run_in_thread(
|
||||
self._client.llama_extract.validate_extraction_schema(
|
||||
request=ExtractSchemaValidateRequest(data_schema=processed_schema)
|
||||
)
|
||||
)
|
||||
self._data_schema = validated_schema.data_schema
|
||||
|
||||
@property
|
||||
def config(self) -> ExtractConfig:
|
||||
return self._agent.config if not self._config else self._config
|
||||
|
||||
@config.setter
|
||||
def config(self, config: ExtractConfig) -> None:
|
||||
self._config = config
|
||||
|
||||
async def _upload_file(self, file_input: FileInput) -> File:
|
||||
"""Upload a file for extraction."""
|
||||
if isinstance(file_input, BufferedIOBase):
|
||||
upload_file = file_input
|
||||
elif isinstance(file_input, bytes):
|
||||
upload_file = BytesIO(file_input)
|
||||
elif isinstance(file_input, (str, Path)):
|
||||
upload_file = open(file_input, "rb")
|
||||
else:
|
||||
raise ValueError(
|
||||
"file_input must be either a file path string, file bytes, or buffer object"
|
||||
)
|
||||
|
||||
try:
|
||||
return await self._client.files.upload_file(
|
||||
project_id=self._project_id, upload_file=upload_file
|
||||
)
|
||||
finally:
|
||||
if isinstance(upload_file, BufferedReader):
|
||||
upload_file.close()
|
||||
|
||||
async def _wait_for_job_result(self, job_id: str) -> Optional[ExtractRun]:
|
||||
"""Wait for and return the results of an extraction job."""
|
||||
start = time.perf_counter()
|
||||
tries = 0
|
||||
while True:
|
||||
await asyncio.sleep(self.check_interval)
|
||||
tries += 1
|
||||
job = await self._client.llama_extract.get_job(
|
||||
job_id=job_id,
|
||||
)
|
||||
|
||||
if job.status == StatusEnum.SUCCESS:
|
||||
return await self._client.llama_extract.get_run_by_job_id(
|
||||
job_id=job_id,
|
||||
)
|
||||
elif job.status == StatusEnum.PENDING:
|
||||
end = time.perf_counter()
|
||||
if end - start > self.max_timeout:
|
||||
raise Exception(f"Timeout while extracting the file: {job_id}")
|
||||
if self._verbose and tries % 10 == 0:
|
||||
print(".", end="", flush=True)
|
||||
continue
|
||||
else:
|
||||
warnings.warn(
|
||||
f"Failure in job: {job_id}, status: {job.status}, error: {job.error}"
|
||||
)
|
||||
return await self._client.llama_extract.get_run_by_job_id(
|
||||
job_id=job_id,
|
||||
)
|
||||
|
||||
def save(self) -> None:
|
||||
"""Persist the extraction agent's schema and config to the database.
|
||||
|
||||
Returns:
|
||||
ExtractionAgent: The updated extraction agent
|
||||
"""
|
||||
self._agent = self._run_in_thread(
|
||||
self._client.llama_extract.update_extraction_agent(
|
||||
extraction_agent_id=self.id,
|
||||
request=ExtractAgentUpdate(
|
||||
data_schema=self.data_schema,
|
||||
config=self.config,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
async def _queue_extraction_test(
|
||||
self,
|
||||
files: Union[FileInput, List[FileInput]],
|
||||
extract_settings: LlamaExtractSettings,
|
||||
) -> Union[ExtractJob, List[ExtractJob]]:
|
||||
if not isinstance(files, list):
|
||||
files = [files]
|
||||
single_file = True
|
||||
else:
|
||||
single_file = False
|
||||
|
||||
upload_tasks = [self._upload_file(file) for file in files]
|
||||
with augment_async_errors():
|
||||
uploaded_files = await run_jobs(
|
||||
upload_tasks,
|
||||
workers=self.num_workers,
|
||||
desc="Uploading files",
|
||||
show_progress=self.show_progress,
|
||||
)
|
||||
|
||||
async def run_job(file: File) -> ExtractRun:
|
||||
job_queued = await self._client.llama_extract.run_job_test_user(
|
||||
job_create=ExtractJobCreate(
|
||||
extraction_agent_id=self.id,
|
||||
file_id=file.id,
|
||||
data_schema_override=self.data_schema,
|
||||
config_override=self.config,
|
||||
),
|
||||
extract_settings=extract_settings,
|
||||
)
|
||||
return await self._wait_for_job_result(job_queued.id)
|
||||
|
||||
job_tasks = [run_job(file) for file in uploaded_files]
|
||||
with augment_async_errors():
|
||||
extract_jobs = await run_jobs(
|
||||
job_tasks,
|
||||
workers=self.num_workers,
|
||||
desc="Running extraction jobs",
|
||||
show_progress=self.show_progress,
|
||||
)
|
||||
|
||||
if self._verbose:
|
||||
for file, job in zip(files, extract_jobs):
|
||||
file_repr = (
|
||||
str(file) if isinstance(file, (str, Path)) else "<bytes/buffer>"
|
||||
)
|
||||
print(
|
||||
f"Queued file extraction for file {file_repr} under job_id {job.id}"
|
||||
)
|
||||
|
||||
return extract_jobs[0] if single_file else extract_jobs
|
||||
|
||||
async def queue_extraction(
|
||||
self,
|
||||
files: Union[FileInput, List[FileInput]],
|
||||
) -> Union[ExtractJob, List[ExtractJob]]:
|
||||
"""
|
||||
Queue multiple files for extraction.
|
||||
|
||||
Args:
|
||||
files (Union[FileInput, List[FileInput]]): The files to extract
|
||||
|
||||
Returns:
|
||||
Union[ExtractJob, List[ExtractJob]]: The queued extraction jobs
|
||||
"""
|
||||
"""Queue one or more files for extraction concurrently."""
|
||||
if not isinstance(files, list):
|
||||
files = [files]
|
||||
single_file = True
|
||||
else:
|
||||
single_file = False
|
||||
|
||||
upload_tasks = [self._upload_file(file) for file in files]
|
||||
with augment_async_errors():
|
||||
uploaded_files = await run_jobs(
|
||||
upload_tasks,
|
||||
workers=self.num_workers,
|
||||
desc="Uploading files",
|
||||
show_progress=self.show_progress,
|
||||
)
|
||||
|
||||
job_tasks = [
|
||||
self._client.llama_extract.run_job(
|
||||
request=ExtractJobCreate(
|
||||
extraction_agent_id=self.id,
|
||||
file_id=file.id,
|
||||
data_schema_override=self.data_schema,
|
||||
config_override=self.config,
|
||||
),
|
||||
)
|
||||
for file in uploaded_files
|
||||
]
|
||||
with augment_async_errors():
|
||||
extract_jobs = await run_jobs(
|
||||
job_tasks,
|
||||
workers=self.num_workers,
|
||||
desc="Creating extraction jobs",
|
||||
show_progress=self.show_progress,
|
||||
)
|
||||
|
||||
if self._verbose:
|
||||
for file, job in zip(files, extract_jobs):
|
||||
file_repr = (
|
||||
str(file) if isinstance(file, (str, Path)) else "<bytes/buffer>"
|
||||
)
|
||||
print(
|
||||
f"Queued file extraction for file {file_repr} under job_id {job.id}"
|
||||
)
|
||||
|
||||
return extract_jobs[0] if single_file else extract_jobs
|
||||
|
||||
async def aextract(
|
||||
self, files: Union[FileInput, List[FileInput]]
|
||||
) -> Union[ExtractRun, List[ExtractRun]]:
|
||||
"""Asynchronously extract data from one or more files using this agent.
|
||||
|
||||
Args:
|
||||
files (Union[FileInput, List[FileInput]]): The files to extract
|
||||
|
||||
Returns:
|
||||
Union[ExtractRun, List[ExtractRun]]: The extraction results
|
||||
"""
|
||||
if not isinstance(files, list):
|
||||
files = [files]
|
||||
single_file = True
|
||||
else:
|
||||
single_file = False
|
||||
|
||||
# Queue all files for extraction
|
||||
jobs = await self.queue_extraction(files)
|
||||
# Wait for all results concurrently
|
||||
result_tasks = [self._wait_for_job_result(job.id) for job in jobs]
|
||||
with augment_async_errors():
|
||||
results = await run_jobs(
|
||||
result_tasks,
|
||||
workers=self.num_workers,
|
||||
desc="Extracting files",
|
||||
show_progress=self.show_progress,
|
||||
)
|
||||
|
||||
return results[0] if single_file else results
|
||||
|
||||
def extract(
|
||||
self, files: Union[FileInput, List[FileInput]]
|
||||
) -> Union[ExtractRun, List[ExtractRun]]:
|
||||
"""Synchronously extract data from one or more files using this agent.
|
||||
|
||||
Args:
|
||||
files (Union[FileInput, List[FileInput]]): The files to extract
|
||||
|
||||
Returns:
|
||||
Union[ExtractRun, List[ExtractRun]]: The extraction results
|
||||
"""
|
||||
return self._run_in_thread(self.aextract(files))
|
||||
|
||||
def get_extraction_job(self, job_id: str) -> ExtractJob:
|
||||
"""
|
||||
Get the extraction job for a given job_id.
|
||||
|
||||
Args:
|
||||
job_id (str): The job_id to get the extraction job for
|
||||
|
||||
Returns:
|
||||
ExtractJob: The extraction job
|
||||
"""
|
||||
return self._run_in_thread(self._client.llama_extract.get_job(job_id=job_id))
|
||||
|
||||
def get_extraction_run_for_job(self, job_id: str) -> ExtractRun:
|
||||
"""
|
||||
Get the extraction run for a given job_id.
|
||||
|
||||
Args:
|
||||
job_id (str): The job_id to get the extraction run for
|
||||
|
||||
Returns:
|
||||
ExtractRun: The extraction run
|
||||
"""
|
||||
return self._run_in_thread(
|
||||
self._client.llama_extract.get_run_by_job_id(
|
||||
job_id=job_id,
|
||||
)
|
||||
)
|
||||
|
||||
def delete_extraction_run(self, run_id: str) -> None:
|
||||
"""Delete an extraction run by ID.
|
||||
|
||||
Args:
|
||||
run_id (str): The ID of the extraction run to delete
|
||||
"""
|
||||
self._run_in_thread(
|
||||
self._client.llama_extract.delete_extraction_run(run_id=run_id)
|
||||
)
|
||||
|
||||
def list_extraction_runs(
|
||||
self, page: int = 0, limit: int = 100
|
||||
) -> PaginatedExtractRunsResponse:
|
||||
"""List extraction runs for the extraction agent.
|
||||
|
||||
Returns:
|
||||
PaginatedExtractRunsResponse: Paginated list of extraction runs
|
||||
"""
|
||||
return self._run_in_thread(
|
||||
self._client.llama_extract.list_extract_runs(
|
||||
extraction_agent_id=self.id,
|
||||
skip=page * limit,
|
||||
limit=limit,
|
||||
)
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ExtractionAgent(id={self.id}, name={self.name})"
|
||||
|
||||
|
||||
class LlamaExtract(BaseComponent):
|
||||
"""Factory class for creating and managing extraction agents."""
|
||||
|
||||
api_key: str = Field(description="The API key for the LlamaExtract API.")
|
||||
base_url: str = Field(description="The base URL of the LlamaExtract API.")
|
||||
check_interval: int = Field(
|
||||
default=1,
|
||||
description="The interval in seconds to check if the extraction is done.",
|
||||
)
|
||||
max_timeout: int = Field(
|
||||
default=2000,
|
||||
description="The maximum timeout in seconds to wait for the extraction to finish.",
|
||||
)
|
||||
num_workers: int = Field(
|
||||
default=4,
|
||||
gt=0,
|
||||
lt=10,
|
||||
description="The number of workers to use sending API requests for extraction.",
|
||||
)
|
||||
show_progress: bool = Field(
|
||||
default=True, description="Show progress when extracting multiple files."
|
||||
)
|
||||
verbose: bool = Field(
|
||||
default=False, description="Show verbose output when extracting files."
|
||||
)
|
||||
verify: Optional[bool] = Field(
|
||||
default=True, description="Simple SSL verification option."
|
||||
)
|
||||
httpx_timeout: Optional[float] = Field(
|
||||
default=60, description="Timeout for the httpx client."
|
||||
)
|
||||
_async_client: AsyncLlamaCloud = PrivateAttr()
|
||||
_thread_pool: ThreadPoolExecutor = PrivateAttr()
|
||||
_project_id: Optional[str] = PrivateAttr()
|
||||
_organization_id: Optional[str] = PrivateAttr()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
check_interval: int = 1,
|
||||
max_timeout: int = 2000,
|
||||
num_workers: int = 4,
|
||||
show_progress: bool = True,
|
||||
project_id: Optional[str] = None,
|
||||
organization_id: Optional[str] = None,
|
||||
verify: Optional[bool] = True,
|
||||
httpx_timeout: Optional[float] = 60,
|
||||
verbose: bool = False,
|
||||
):
|
||||
if not api_key:
|
||||
api_key = os.getenv("LLAMA_CLOUD_API_KEY", None)
|
||||
if api_key is None:
|
||||
raise ValueError("The API key is required.")
|
||||
|
||||
if not base_url:
|
||||
base_url = os.getenv("LLAMA_CLOUD_BASE_URL", None) or DEFAULT_BASE_URL
|
||||
|
||||
super().__init__(
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
check_interval=check_interval,
|
||||
max_timeout=max_timeout,
|
||||
num_workers=num_workers,
|
||||
show_progress=show_progress,
|
||||
verify=verify,
|
||||
httpx_timeout=httpx_timeout,
|
||||
verbose=verbose,
|
||||
)
|
||||
self._httpx_client = httpx.AsyncClient(verify=verify, timeout=httpx_timeout)
|
||||
self.verify = verify
|
||||
self.httpx_timeout = httpx_timeout
|
||||
|
||||
self._async_client = AsyncLlamaCloud(
|
||||
token=self.api_key,
|
||||
base_url=self.base_url,
|
||||
httpx_client=self._httpx_client,
|
||||
)
|
||||
self._thread_pool = ThreadPoolExecutor(
|
||||
max_workers=min(10, (os.cpu_count() or 1) + 4)
|
||||
)
|
||||
# Fetch default project id if not provided
|
||||
if not project_id:
|
||||
project_id = os.getenv("LLAMA_CLOUD_PROJECT_ID", None)
|
||||
if not project_id:
|
||||
print("No project_id provided, fetching default project.")
|
||||
projects: List[Project] = self._run_in_thread(
|
||||
self._async_client.projects.list_projects()
|
||||
)
|
||||
default_project = [p for p in projects if p.is_default]
|
||||
if not default_project:
|
||||
raise ValueError(
|
||||
"No default project found. Please provide a project_id."
|
||||
)
|
||||
project_id = default_project[0].id
|
||||
|
||||
self._project_id = project_id
|
||||
self._organization_id = organization_id
|
||||
|
||||
def _run_in_thread(self, coro: Coroutine[Any, Any, T]) -> T:
|
||||
"""Run coroutine in a separate thread to avoid event loop issues"""
|
||||
|
||||
def run_coro() -> T:
|
||||
# Create a new client for this thread
|
||||
async def wrapped_coro() -> T:
|
||||
assert (
|
||||
self._httpx_client is not None
|
||||
), "httpx_client should be initialized"
|
||||
# Create a new client with the same configuration as the original
|
||||
async with httpx.AsyncClient(
|
||||
verify=self.verify,
|
||||
timeout=self.httpx_timeout,
|
||||
) as client:
|
||||
# Temporarily replace the client
|
||||
self._async_client._client_wrapper.httpx_client = client
|
||||
try:
|
||||
return await coro
|
||||
finally:
|
||||
# Restore the original client
|
||||
self._async_client._client_wrapper.httpx_client = (
|
||||
self._httpx_client
|
||||
)
|
||||
|
||||
return asyncio.run(wrapped_coro())
|
||||
|
||||
return self._thread_pool.submit(run_coro).result()
|
||||
|
||||
def create_agent(
|
||||
self,
|
||||
name: str,
|
||||
data_schema: SchemaInput,
|
||||
config: Optional[ExtractConfig] = None,
|
||||
) -> ExtractionAgent:
|
||||
"""Create a new extraction agent.
|
||||
|
||||
Args:
|
||||
name (str): The name of the extraction agent
|
||||
data_schema (SchemaInput): The data schema for the extraction agent
|
||||
config (Optional[ExtractConfig]): The extraction config for the agent
|
||||
|
||||
Returns:
|
||||
ExtractionAgent: The created extraction agent
|
||||
"""
|
||||
if config is not None:
|
||||
if config.extraction_mode == ExtractMode.ACCURATE:
|
||||
warnings.warn(
|
||||
"ACCURATE extraction mode is deprecated. Using BALANCED instead."
|
||||
)
|
||||
config.extraction_mode = ExtractMode.BALANCED
|
||||
else:
|
||||
config = DEFAULT_EXTRACT_CONFIG
|
||||
|
||||
if isinstance(data_schema, dict):
|
||||
data_schema = data_schema
|
||||
elif issubclass(data_schema, BaseModel):
|
||||
data_schema = data_schema.model_json_schema()
|
||||
else:
|
||||
raise ValueError(
|
||||
"data_schema must be either a dictionary or a Pydantic model"
|
||||
)
|
||||
|
||||
agent = self._run_in_thread(
|
||||
self._async_client.llama_extract.create_extraction_agent(
|
||||
project_id=self._project_id,
|
||||
organization_id=self._organization_id,
|
||||
request=ExtractAgentCreate(
|
||||
name=name,
|
||||
data_schema=data_schema,
|
||||
config=config,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
return ExtractionAgent(
|
||||
client=self._async_client,
|
||||
agent=agent,
|
||||
project_id=self._project_id,
|
||||
organization_id=self._organization_id,
|
||||
check_interval=self.check_interval,
|
||||
max_timeout=self.max_timeout,
|
||||
num_workers=self.num_workers,
|
||||
show_progress=self.show_progress,
|
||||
verbose=self.verbose,
|
||||
)
|
||||
|
||||
def get_agent(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
id: Optional[str] = None,
|
||||
) -> ExtractionAgent:
|
||||
"""Get extraction agents by name or extraction agent ID.
|
||||
|
||||
Args:
|
||||
name (Optional[str]): Filter by name
|
||||
extraction_agent_id (Optional[str]): Filter by extraction agent ID
|
||||
|
||||
Returns:
|
||||
ExtractionAgent: The extraction agent
|
||||
"""
|
||||
if id is not None and name is not None:
|
||||
warnings.warn(
|
||||
"Both name and extraction_agent_id are provided. Using extraction_agent_id."
|
||||
)
|
||||
|
||||
if id:
|
||||
agent = self._run_in_thread(
|
||||
self._async_client.llama_extract.get_extraction_agent(
|
||||
extraction_agent_id=id,
|
||||
)
|
||||
)
|
||||
|
||||
elif name:
|
||||
agent = self._run_in_thread(
|
||||
self._async_client.llama_extract.get_extraction_agent_by_name(
|
||||
name=name,
|
||||
project_id=self._project_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise ValueError("Either name or extraction_agent_id must be provided.")
|
||||
|
||||
return ExtractionAgent(
|
||||
client=self._async_client,
|
||||
agent=agent,
|
||||
project_id=self._project_id,
|
||||
organization_id=self._organization_id,
|
||||
check_interval=self.check_interval,
|
||||
max_timeout=self.max_timeout,
|
||||
num_workers=self.num_workers,
|
||||
show_progress=self.show_progress,
|
||||
verbose=self.verbose,
|
||||
verify=self.verify,
|
||||
httpx_timeout=self.httpx_timeout,
|
||||
)
|
||||
|
||||
def list_agents(self) -> List[ExtractionAgent]:
|
||||
"""List all available extraction agents."""
|
||||
agents = self._run_in_thread(
|
||||
self._async_client.llama_extract.list_extraction_agents(
|
||||
project_id=self._project_id,
|
||||
)
|
||||
)
|
||||
|
||||
return [
|
||||
ExtractionAgent(
|
||||
client=self._async_client,
|
||||
agent=agent,
|
||||
project_id=self._project_id,
|
||||
organization_id=self._organization_id,
|
||||
check_interval=self.check_interval,
|
||||
max_timeout=self.max_timeout,
|
||||
num_workers=self.num_workers,
|
||||
show_progress=self.show_progress,
|
||||
verbose=self.verbose,
|
||||
)
|
||||
for agent in agents
|
||||
]
|
||||
|
||||
def delete_agent(self, agent_id: str) -> None:
|
||||
"""Delete an extraction agent by ID.
|
||||
|
||||
Args:
|
||||
agent_id (str): ID of the extraction agent to delete
|
||||
"""
|
||||
self._run_in_thread(
|
||||
self._async_client.llama_extract.delete_extraction_agent(
|
||||
extraction_agent_id=agent_id
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
data_dir = Path(__file__).parent.parent / "tests" / "data"
|
||||
extractor = LlamaExtract()
|
||||
try:
|
||||
agent = extractor.get_agent(name="test-agent")
|
||||
except Exception:
|
||||
agent = extractor.create_agent(
|
||||
"test-agent",
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string"},
|
||||
"summary": {"type": "string"},
|
||||
},
|
||||
},
|
||||
)
|
||||
results = agent.extract(data_dir / "slide" / "conocophilips.pdf")
|
||||
extractor.delete_agent(agent.id)
|
||||
print(results)
|
||||
@@ -1,34 +0,0 @@
|
||||
from typing import Any, Dict, List, Union, Generator
|
||||
from contextlib import contextmanager
|
||||
|
||||
# Asyncio error messages
|
||||
nest_asyncio_err = "cannot be called from a running event loop"
|
||||
nest_asyncio_msg = (
|
||||
"The event loop is already running. "
|
||||
"Add `import nest_asyncio; nest_asyncio.apply()` to your code to fix this issue."
|
||||
)
|
||||
|
||||
|
||||
def is_jupyter() -> bool:
|
||||
"""Check if we're running in a Jupyter environment."""
|
||||
try:
|
||||
from IPython import get_ipython
|
||||
|
||||
return get_ipython().__class__.__name__ == "ZMQInteractiveShell"
|
||||
except (ImportError, AttributeError):
|
||||
return False
|
||||
|
||||
|
||||
@contextmanager
|
||||
def augment_async_errors() -> Generator[None, None, None]:
|
||||
"""Context manager to add helpful information for errors due to nested event loops."""
|
||||
try:
|
||||
yield
|
||||
except RuntimeError as e:
|
||||
if nest_asyncio_err in str(e):
|
||||
raise RuntimeError(nest_asyncio_msg)
|
||||
raise
|
||||
|
||||
|
||||
JSONType = Union[Dict[str, Any], List[Any], str, int, float, bool, None]
|
||||
JSONObjectType = Dict[str, JSONType]
|
||||
@@ -1,3 +0,0 @@
|
||||
from llama_cloud_services.parse.base import LlamaParse, ResultType
|
||||
|
||||
__all__ = ["LlamaParse", "ResultType"]
|
||||
@@ -1,211 +0,0 @@
|
||||
from enum import Enum
|
||||
|
||||
# Asyncio error messages
|
||||
nest_asyncio_err = "cannot be called from a running event loop"
|
||||
nest_asyncio_msg = "The event loop is already running. Add `import nest_asyncio; nest_asyncio.apply()` to your code to fix this issue."
|
||||
|
||||
|
||||
class ResultType(str, Enum):
|
||||
"""The result type for the parser."""
|
||||
|
||||
TXT = "text"
|
||||
MD = "markdown"
|
||||
JSON = "json"
|
||||
STRUCTURED = "structured"
|
||||
|
||||
|
||||
class ParsingMode(str, Enum):
|
||||
"""The parsing mode for the parser."""
|
||||
|
||||
parse_page_without_llm = "parse_page_without_llm"
|
||||
parse_page_with_llm = "parse_page_with_llm"
|
||||
parse_page_with_lvm = "parse_page_with_lvm"
|
||||
parse_page_with_agent = "parse_page_with_agent"
|
||||
parse_document_with_llm = "parse_document_with_llm"
|
||||
|
||||
|
||||
class Language(str, Enum):
|
||||
BAZA = "abq"
|
||||
ADYGHE = "ady"
|
||||
AFRIKAANS = "af"
|
||||
ANGIKA = "ang"
|
||||
ARABIC = "ar"
|
||||
ASSAMESE = "as"
|
||||
AVAR = "ava"
|
||||
AZERBAIJANI = "az"
|
||||
BELARUSIAN = "be"
|
||||
BULGARIAN = "bg"
|
||||
BIHARI = "bh"
|
||||
BHOJPURI = "bho"
|
||||
BENGALI = "bn"
|
||||
BOSNIAN = "bs"
|
||||
SIMPLIFIED_CHINESE = "ch_sim"
|
||||
TRADITIONAL_CHINESE = "ch_tra"
|
||||
CHECHEN = "che"
|
||||
CZECH = "cs"
|
||||
WELSH = "cy"
|
||||
DANISH = "da"
|
||||
DARGWA = "dar"
|
||||
GERMAN = "de"
|
||||
ENGLISH = "en"
|
||||
SPANISH = "es"
|
||||
ESTONIAN = "et"
|
||||
PERSIAN_FARSI = "fa"
|
||||
FRENCH = "fr"
|
||||
IRISH = "ga"
|
||||
GOAN_KONKANI = "gom"
|
||||
HINDI = "hi"
|
||||
CROATIAN = "hr"
|
||||
HUNGARIAN = "hu"
|
||||
INDONESIAN = "id"
|
||||
INGUSH = "inh"
|
||||
ICELANDIC = "is"
|
||||
ITALIAN = "it"
|
||||
JAPANESE = "ja"
|
||||
KABARDIAN = "kbd"
|
||||
KANNADA = "kn"
|
||||
KOREAN = "ko"
|
||||
KURDISH = "ku"
|
||||
LATIN = "la"
|
||||
LAK = "lbe"
|
||||
LEZGHIAN = "lez"
|
||||
LITHUANIAN = "lt"
|
||||
LATVIAN = "lv"
|
||||
MAGAHI = "mah"
|
||||
MAITHILI = "mai"
|
||||
MAORI = "mi"
|
||||
MONGOLIAN = "mn"
|
||||
MARATHI = "mr"
|
||||
MALAY = "ms"
|
||||
MALTESE = "mt"
|
||||
NEPALI = "ne"
|
||||
NEWARI = "new"
|
||||
DUTCH = "nl"
|
||||
NORWEGIAN = "no"
|
||||
OCCITAN = "oc"
|
||||
PALI = "pi"
|
||||
POLISH = "pl"
|
||||
PORTUGUESE = "pt"
|
||||
ROMANIAN = "ro"
|
||||
RUSSIAN = "ru"
|
||||
SERBIAN_CYRILLIC = "rs_cyrillic"
|
||||
SERBIAN_LATIN = "rs_latin"
|
||||
NAGPURI = "sck"
|
||||
SLOVAK = "sk"
|
||||
SLOVENIAN = "sl"
|
||||
ALBANIAN = "sq"
|
||||
SWEDISH = "sv"
|
||||
SWAHILI = "sw"
|
||||
TAMIL = "ta"
|
||||
TABASSARAN = "tab"
|
||||
TELUGU = "te"
|
||||
THAI = "th"
|
||||
TAJIK = "tjk"
|
||||
TAGALOG = "tl"
|
||||
TURKISH = "tr"
|
||||
UYGHUR = "ug"
|
||||
UKRAINIAN = "uk"
|
||||
URDU = "ur"
|
||||
UZBEK = "uz"
|
||||
VIETNAMESE = "vi"
|
||||
|
||||
|
||||
SUPPORTED_FILE_TYPES = [
|
||||
".pdf",
|
||||
# document and presentations
|
||||
".602",
|
||||
".abw",
|
||||
".cgm",
|
||||
".cwk",
|
||||
".doc",
|
||||
".docx",
|
||||
".docm",
|
||||
".dot",
|
||||
".dotm",
|
||||
".hwp",
|
||||
".key",
|
||||
".lwp",
|
||||
".mw",
|
||||
".mcw",
|
||||
".pages",
|
||||
".pbd",
|
||||
".ppt",
|
||||
".pptm",
|
||||
".pptx",
|
||||
".pot",
|
||||
".potm",
|
||||
".potx",
|
||||
".rtf",
|
||||
".sda",
|
||||
".sdd",
|
||||
".sdp",
|
||||
".sdw",
|
||||
".sgl",
|
||||
".sti",
|
||||
".sxi",
|
||||
".sxw",
|
||||
".stw",
|
||||
".sxg",
|
||||
".txt",
|
||||
".uof",
|
||||
".uop",
|
||||
".uot",
|
||||
".vor",
|
||||
".wpd",
|
||||
".wps",
|
||||
".xml",
|
||||
".zabw",
|
||||
".epub",
|
||||
# images
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".gif",
|
||||
".bmp",
|
||||
".svg",
|
||||
".tiff",
|
||||
".webp",
|
||||
# web
|
||||
".htm",
|
||||
".html",
|
||||
# spreadsheets
|
||||
".xlsx",
|
||||
".xls",
|
||||
".xlsm",
|
||||
".xlsb",
|
||||
".xlw",
|
||||
".csv",
|
||||
".dif",
|
||||
".sylk",
|
||||
".slk",
|
||||
".prn",
|
||||
".numbers",
|
||||
".et",
|
||||
".ods",
|
||||
".fods",
|
||||
".uos1",
|
||||
".uos2",
|
||||
".dbf",
|
||||
".wk1",
|
||||
".wk2",
|
||||
".wk3",
|
||||
".wk4",
|
||||
".wks",
|
||||
".123",
|
||||
".wq1",
|
||||
".wq2",
|
||||
".wb1",
|
||||
".wb2",
|
||||
".wb3",
|
||||
".qpw",
|
||||
".xlr",
|
||||
".eth",
|
||||
".tsv",
|
||||
".mp3",
|
||||
".mp4",
|
||||
".mpeg",
|
||||
".mpga",
|
||||
".m4a",
|
||||
".wav",
|
||||
".webm",
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
from llama_cloud_services.parse import LlamaParse, ResultType
|
||||
|
||||
__all__ = ["LlamaParse", "ResultType"]
|
||||
@@ -1,24 +0,0 @@
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry]
|
||||
name = "llama-parse"
|
||||
version = "0.6.9"
|
||||
description = "Parse files into RAG-Optimized formats."
|
||||
authors = ["Logan Markewich <logan@llamaindex.ai>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
packages = [{include = "llama_parse"}]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9,<4.0"
|
||||
llama-cloud-services = ">=0.6.9"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^8.0.0"
|
||||
pytest-asyncio = "*"
|
||||
ipykernel = "^6.29.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
llama-parse = "llama_parse.cli.main:parse"
|
||||
@@ -25,6 +25,8 @@ Then, install the package:
|
||||
|
||||
`pip install llama-cloud-services`
|
||||
|
||||
## CLI Usage
|
||||
|
||||
Now you can parse your first PDF file using the command line interface. Use the command `llama-parse [file_paths]`. See the help text with `llama-parse --help`.
|
||||
|
||||
```bash
|
||||
@@ -40,50 +42,72 @@ llama-parse my_file.pdf --result-type markdown --output-file output.md
|
||||
llama-parse my_file.pdf --output-raw-json --output-file output.json
|
||||
```
|
||||
|
||||
## Python Usage
|
||||
|
||||
You can also create simple scripts:
|
||||
|
||||
```python
|
||||
import nest_asyncio
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
from llama_cloud_services import LlamaParse
|
||||
|
||||
parser = LlamaParse(
|
||||
api_key="llx-...", # can also be set in your env as LLAMA_CLOUD_API_KEY
|
||||
result_type="markdown", # "markdown" and "text" are available
|
||||
num_workers=4, # if multiple files passed, split in `num_workers` API calls
|
||||
verbose=True,
|
||||
language="en", # Optionally you can define a language, default=en
|
||||
)
|
||||
|
||||
# sync
|
||||
documents = parser.load_data("./my_file.pdf")
|
||||
result = parser.parse("./my_file.pdf")
|
||||
|
||||
# sync batch
|
||||
documents = parser.load_data(["./my_file1.pdf", "./my_file2.pdf"])
|
||||
results = parser.parse(["./my_file1.pdf", "./my_file2.pdf"])
|
||||
|
||||
# async
|
||||
documents = await parser.aload_data("./my_file.pdf")
|
||||
result = await parser.aparse("./my_file.pdf")
|
||||
|
||||
# async batch
|
||||
documents = await parser.aload_data(["./my_file1.pdf", "./my_file2.pdf"])
|
||||
results = await parser.aparse(["./my_file1.pdf", "./my_file2.pdf"])
|
||||
```
|
||||
|
||||
## Using with file object
|
||||
The result object is a fully typed `JobResult` object, and you can interact with it to parse and transform various parts of the result:
|
||||
|
||||
```python
|
||||
# get the llama-index markdown documents
|
||||
markdown_documents = result.get_markdown_documents(split_by_page=True)
|
||||
|
||||
# get the llama-index text documents
|
||||
text_documents = result.get_text_documents(split_by_page=False)
|
||||
|
||||
# get the image documents
|
||||
image_documents = result.get_image_documents(
|
||||
include_screenshot_images=True,
|
||||
include_object_images=False,
|
||||
# Optional: download the images to a directory
|
||||
# (default is to return the image bytes in ImageDocument objects)
|
||||
image_download_dir="./images",
|
||||
)
|
||||
|
||||
# access the raw job result
|
||||
# Items will vary based on the parser configuration
|
||||
for page in result.pages:
|
||||
print(page.text)
|
||||
print(page.md)
|
||||
print(page.images)
|
||||
print(page.layout)
|
||||
print(page.structuredData)
|
||||
```
|
||||
|
||||
See more details about the result object in the [example notebook](./docs/examples-py/parse/demo_json_tour.ipynb).
|
||||
|
||||
### Using with file object / bytes
|
||||
|
||||
You can parse a file object directly:
|
||||
|
||||
```python
|
||||
import nest_asyncio
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
from llama_cloud_services import LlamaParse
|
||||
|
||||
parser = LlamaParse(
|
||||
api_key="llx-...", # can also be set in your env as LLAMA_CLOUD_API_KEY
|
||||
result_type="markdown", # "markdown" and "text" are available
|
||||
num_workers=4, # if multiple files passed, split in `num_workers` API calls
|
||||
verbose=True,
|
||||
language="en", # Optionally you can define a language, default=en
|
||||
@@ -94,24 +118,20 @@ extra_info = {"file_name": file_name}
|
||||
|
||||
with open(f"./{file_name}", "rb") as f:
|
||||
# must provide extra_info with file_name key with passing file object
|
||||
documents = parser.load_data(f, extra_info=extra_info)
|
||||
result = parser.parse(f, extra_info=extra_info)
|
||||
|
||||
# you can also pass file bytes directly
|
||||
with open(f"./{file_name}", "rb") as f:
|
||||
file_bytes = f.read()
|
||||
# must provide extra_info with file_name key with passing file bytes
|
||||
documents = parser.load_data(file_bytes, extra_info=extra_info)
|
||||
result = parser.parse(file_bytes, extra_info=extra_info)
|
||||
```
|
||||
|
||||
## Using with `SimpleDirectoryReader`
|
||||
### Using with `SimpleDirectoryReader`
|
||||
|
||||
You can also integrate the parser as the default PDF loader in `SimpleDirectoryReader`:
|
||||
|
||||
```python
|
||||
import nest_asyncio
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
from llama_cloud_services import LlamaParse
|
||||
from llama_index.core import SimpleDirectoryReader
|
||||
|
||||
@@ -133,9 +153,10 @@ Full documentation for `SimpleDirectoryReader` can be found on the [LlamaIndex D
|
||||
|
||||
Several end-to-end indexing examples can be found in the examples folder
|
||||
|
||||
- [Getting Started](examples/parse/demo_basic.ipynb)
|
||||
- [Advanced RAG Example](examples/parse/demo_advanced.ipynb)
|
||||
- [Raw API Usage](examples/parse/demo_api.ipynb)
|
||||
- [Getting Started](docs/examples-py/parse/demo_basic.ipynb)
|
||||
- [Advanced RAG Example](docs/examples-py/parse/demo_advanced.ipynb)
|
||||
- [Raw API Usage](docs/examples-py/parse/demo_api.ipynb)
|
||||
- [Result Object Tour](docs/examples-py/parse/demo_json_tour.ipynb)
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
Generated
+3609
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- "ts/**"
|
||||
Generated
-4411
File diff suppressed because it is too large
Load Diff
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 LlamaIndex
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,87 @@
|
||||
[](https://pypi.org/project/llama-cloud-services/)
|
||||
[](https://github.com/run-llama/llama_cloud_services/graphs/contributors)
|
||||
[](https://discord.gg/dGcwcsnxhU)
|
||||
|
||||
# Llama Cloud Services
|
||||
|
||||
This repository contains the code for hand-written SDKs and clients for interacting with LlamaCloud.
|
||||
|
||||
This includes:
|
||||
|
||||
- [LlamaParse](../parse.md) - A GenAI-native document parser that can parse complex document data for any downstream LLM use case (Agents, RAG, data processing, etc.).
|
||||
- [LlamaReport (beta/invite-only)](../report.md) - A prebuilt agentic report builder that can be used to build reports from a variety of data sources.
|
||||
- [LlamaExtract](../extract.md) - A prebuilt agentic data extractor that can be used to transform data into a structured JSON representation.
|
||||
- [LlamaCloud Index](../index.md) - A widely customizable and fully automated document ingestion pipeline that also serves retrieval purposes.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Install the package:
|
||||
|
||||
```bash
|
||||
pip install llama-cloud-services
|
||||
```
|
||||
|
||||
Then, get your API key from [LlamaCloud](https://cloud.llamaindex.ai/).
|
||||
|
||||
Then, you can use the services in your code:
|
||||
|
||||
```python
|
||||
from llama_cloud_services import (
|
||||
LlamaParse,
|
||||
LlamaReport,
|
||||
LlamaExtract,
|
||||
LlamaCloudIndex,
|
||||
)
|
||||
from llama_cloud_services import LlamaParse, LlamaReport, LlamaExtract
|
||||
|
||||
parser = LlamaParse(api_key="YOUR_API_KEY")
|
||||
report = LlamaReport(api_key="YOUR_API_KEY")
|
||||
extract = LlamaExtract(api_key="YOUR_API_KEY")
|
||||
index = LlamaCloudIndex(
|
||||
"my_first_index", project_name="default", api_key="YOUR_API_KEY"
|
||||
)
|
||||
```
|
||||
|
||||
See the quickstart guides for each service for more information:
|
||||
|
||||
- [LlamaParse](../parse.md)
|
||||
- [LlamaReport (beta/invite-only)](../report.md)
|
||||
- [LlamaExtract](../extract.md)
|
||||
- [LlamaCloud Index](../index.md)
|
||||
|
||||
## Switch to EU SaaS 🇪🇺
|
||||
|
||||
If you are interested in using LlamaCloud services in the EU, you can adjust your base URL to `https://api.cloud.eu.llamaindex.ai`.
|
||||
|
||||
You can also create your API key in the EU region [here](https://cloud.eu.llamaindex.ai).
|
||||
|
||||
```python
|
||||
from llama_cloud_services import (
|
||||
LlamaParse,
|
||||
LlamaReport,
|
||||
LlamaExtract,
|
||||
EU_BASE_URL,
|
||||
)
|
||||
|
||||
parser = LlamaParse(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
report = LlamaReport(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
extract = LlamaExtract(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
index = LlamaCloudIndex(
|
||||
"my_first_index",
|
||||
project_name="default",
|
||||
api_key="YOUR_API_KEY",
|
||||
base_url=EU_BASE_URL,
|
||||
)
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
You can see complete SDK and API documentation for each service on [our official docs](https://docs.cloud.llamaindex.ai/).
|
||||
|
||||
## Terms of Service
|
||||
|
||||
See the [Terms of Service Here](../TOS.pdf).
|
||||
|
||||
## Get in Touch (LlamaCloud)
|
||||
|
||||
You can get in touch with us by following our [contact link](https://www.llamaindex.ai/contact).
|
||||
@@ -2,6 +2,11 @@ from llama_cloud_services.parse import LlamaParse
|
||||
from llama_cloud_services.report import ReportClient, LlamaReport
|
||||
from llama_cloud_services.extract import LlamaExtract, ExtractionAgent
|
||||
from llama_cloud_services.constants import EU_BASE_URL
|
||||
from llama_cloud_services.index import (
|
||||
LlamaCloudCompositeRetriever,
|
||||
LlamaCloudIndex,
|
||||
LlamaCloudRetriever,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"LlamaParse",
|
||||
@@ -10,4 +15,7 @@ __all__ = [
|
||||
"LlamaExtract",
|
||||
"ExtractionAgent",
|
||||
"EU_BASE_URL",
|
||||
"LlamaCloudIndex",
|
||||
"LlamaCloudRetriever",
|
||||
"LlamaCloudCompositeRetriever",
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
from .schema import (
|
||||
TypedAgentData,
|
||||
ExtractedData,
|
||||
TypedAgentDataItems,
|
||||
StatusType,
|
||||
ExtractedT,
|
||||
AgentDataT,
|
||||
ComparisonOperator,
|
||||
parse_extracted_field_metadata,
|
||||
calculate_overall_confidence,
|
||||
InvalidExtractionData,
|
||||
ExtractedFieldMetadata,
|
||||
ExtractedFieldMetaDataDict,
|
||||
)
|
||||
from .client import AsyncAgentDataClient
|
||||
|
||||
__all__ = [
|
||||
"TypedAgentData",
|
||||
"AsyncAgentDataClient",
|
||||
"ExtractedData",
|
||||
"TypedAgentDataItems",
|
||||
"StatusType",
|
||||
"ExtractedT",
|
||||
"AgentDataT",
|
||||
"ComparisonOperator",
|
||||
"parse_extracted_field_metadata",
|
||||
"calculate_overall_confidence",
|
||||
"InvalidExtractionData",
|
||||
"ExtractedFieldMetadata",
|
||||
"ExtractedFieldMetaDataDict",
|
||||
]
|
||||
@@ -0,0 +1,274 @@
|
||||
import os
|
||||
from typing import Any, Dict, Generic, List, Optional, Type
|
||||
|
||||
from llama_cloud.client import AsyncLlamaCloud
|
||||
from tenacity import (
|
||||
WrappedFn,
|
||||
retry,
|
||||
stop_after_attempt,
|
||||
wait_exponential,
|
||||
retry_if_exception_type,
|
||||
)
|
||||
import httpx
|
||||
|
||||
from .schema import (
|
||||
AgentDataT,
|
||||
ComparisonOperator,
|
||||
TypedAgentData,
|
||||
TypedAgentDataItems,
|
||||
TypedAggregateGroup,
|
||||
TypedAggregateGroupItems,
|
||||
)
|
||||
|
||||
|
||||
def agent_data_retry(func: WrappedFn) -> WrappedFn:
|
||||
"""
|
||||
Decorator that adds automatic retry logic to agent data API calls.
|
||||
|
||||
Applies exponential backoff retry strategy for common network-related exceptions:
|
||||
- Up to 3 retry attempts
|
||||
- Exponential wait time between 0.5s and 10s
|
||||
- Retries on timeout, connection, and HTTP status errors
|
||||
|
||||
This ensures resilient API communication in distributed environments where
|
||||
temporary network issues or service unavailability may occur.
|
||||
"""
|
||||
return retry(
|
||||
stop=stop_after_attempt(3),
|
||||
wait=wait_exponential(min=0.5, max=10),
|
||||
retry=retry_if_exception_type(
|
||||
(httpx.TimeoutException, httpx.ConnectError, httpx.HTTPStatusError)
|
||||
),
|
||||
)(func)
|
||||
|
||||
|
||||
def get_default_agent_id() -> str:
|
||||
"""
|
||||
Retrieve the default agent ID from environment variables.
|
||||
|
||||
Returns:
|
||||
The value of LLAMA_DEPLOY_DEPLOYMENT_NAME environment variable,
|
||||
or None if not set
|
||||
|
||||
Note:
|
||||
This provides a convenient way to configure agent ID globally
|
||||
via environment variables instead of passing it explicitly
|
||||
to each client instance.
|
||||
"""
|
||||
return os.getenv("LLAMA_DEPLOY_DEPLOYMENT_NAME") or "_public"
|
||||
|
||||
|
||||
class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
"""
|
||||
Async client for managing agent-generated structured data with type safety.
|
||||
|
||||
This client provides a high-level interface for CRUD operations, searching, and
|
||||
aggregation of structured data created by agents. It enforces type safety by
|
||||
validating all data against a specified Pydantic model type.
|
||||
|
||||
The client is generic over AgentDataT, which must be a Pydantic BaseModel that
|
||||
defines the structure of your agent's data output.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from llama_cloud.client import AsyncLlamaCloud
|
||||
from llama_cloud_services.beta.agent_data import AsyncAgentDataClient
|
||||
|
||||
class ExtractedPerson(BaseModel):
|
||||
name: str
|
||||
age: int
|
||||
email: str
|
||||
|
||||
# Initialize client
|
||||
llama_client = AsyncLlamaCloud(token="your-api-key")
|
||||
agent_client = AsyncAgentDataClient(
|
||||
client=llama_client,
|
||||
type=ExtractedPerson,
|
||||
collection="extracted_people",
|
||||
agent_url_id="person-extraction-agent"
|
||||
)
|
||||
|
||||
# Create data
|
||||
person = ExtractedPerson(name="John Doe", age=30, email="john@example.com")
|
||||
result = await agent_client.create_agent_data(person)
|
||||
|
||||
# Search data
|
||||
results = await agent_client.search(
|
||||
filter={"age": {"gt": 25}},
|
||||
order_by="data.name",
|
||||
page_size=20
|
||||
)
|
||||
```
|
||||
|
||||
Type Parameters:
|
||||
AgentDataT: Pydantic BaseModel type that defines the structure of agent data
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
type: Type[AgentDataT],
|
||||
collection: str = "default",
|
||||
agent_url_id: Optional[str] = None,
|
||||
client: Optional[AsyncLlamaCloud] = None,
|
||||
token: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Initialize the AsyncAgentDataClient.
|
||||
|
||||
Args:
|
||||
type: Pydantic BaseModel class that defines the data structure.
|
||||
All agent data will be validated against this type.
|
||||
collection: Named collection within the agent for organizing data.
|
||||
Defaults to "default". Collections allow logical separation of
|
||||
different data types or workflows within the same agent.
|
||||
agent_url_id: Unique identifier for the agent. This normally appears in the
|
||||
url of an agent within the llama cloud platform. If not provided,
|
||||
will attempt to use the LLAMA_DEPLOY_DEPLOYMENT_NAME environment
|
||||
variable. Data can only be added to an already existing agent in the
|
||||
platform.
|
||||
client: AsyncLlamaCloud client instance for API communication. If not provided, will
|
||||
construct one from the provided api token and base url
|
||||
token: Llama Cloud API token. Reads from LLAMA_CLOUD_API_KEY if not provided
|
||||
base_url: Llama Cloud API token. Reads from LLAMA_CLOUD_BASE_URL if not provided, and
|
||||
defaults to https://api.cloud.llamaindex.ai
|
||||
|
||||
Raises:
|
||||
ValueError: If agent_url_id is not provided and the
|
||||
LLAMA_DEPLOY_DEPLOYMENT_NAME environment variable is not set
|
||||
|
||||
Note:
|
||||
The client automatically applies retry logic to all API calls with
|
||||
exponential backoff for timeout, connection, and HTTP status errors.
|
||||
"""
|
||||
|
||||
self.agent_url_id = agent_url_id or get_default_agent_id()
|
||||
|
||||
self.collection = collection
|
||||
if not client:
|
||||
client = AsyncLlamaCloud(
|
||||
token=token or os.getenv("LLAMA_CLOUD_API_KEY"),
|
||||
base_url=base_url or os.getenv("LLAMA_CLOUD_BASE_URL"),
|
||||
)
|
||||
self.client = client
|
||||
self.type = type
|
||||
|
||||
@agent_data_retry
|
||||
async def get_item(self, item_id: str) -> TypedAgentData[AgentDataT]:
|
||||
raw_data = await self.client.beta.get_agent_data(
|
||||
item_id=item_id,
|
||||
)
|
||||
return TypedAgentData.from_raw(raw_data, validator=self.type)
|
||||
|
||||
@agent_data_retry
|
||||
async def create_item(self, data: AgentDataT) -> TypedAgentData[AgentDataT]:
|
||||
raw_data = await self.client.beta.create_agent_data(
|
||||
agent_slug=self.agent_url_id,
|
||||
collection=self.collection,
|
||||
data=data.model_dump(),
|
||||
)
|
||||
return TypedAgentData.from_raw(raw_data, validator=self.type)
|
||||
|
||||
@agent_data_retry
|
||||
async def update_item(
|
||||
self, item_id: str, data: AgentDataT
|
||||
) -> TypedAgentData[AgentDataT]:
|
||||
raw_data = await self.client.beta.update_agent_data(
|
||||
item_id=item_id,
|
||||
data=data.model_dump(),
|
||||
)
|
||||
return TypedAgentData.from_raw(raw_data, validator=self.type)
|
||||
|
||||
@agent_data_retry
|
||||
async def delete_item(self, item_id: str) -> None:
|
||||
await self.client.beta.delete_agent_data(item_id=item_id)
|
||||
|
||||
@agent_data_retry
|
||||
async def search(
|
||||
self,
|
||||
filter: Optional[Dict[str, Dict[ComparisonOperator, Any]]] = None,
|
||||
order_by: Optional[str] = None,
|
||||
offset: Optional[int] = None,
|
||||
page_size: Optional[int] = None,
|
||||
include_total: bool = False,
|
||||
) -> TypedAgentDataItems[AgentDataT]:
|
||||
"""
|
||||
Search agent data with filtering, sorting, and pagination.
|
||||
Args:
|
||||
filter: Filter conditions to apply to the search. Dict mapping field names to FilterOperation objects. Filters only by data fields
|
||||
Examples:
|
||||
- {"age": {"gt": 18}} - age greater than 18
|
||||
- {"status": {"eq": "active"}} - status equals "active"
|
||||
- {"tags": {"includes": ["python", "ml"]}} - tags include "python" or "ml"
|
||||
- {"created_at": {"gte": "2024-01-01"}} - created after date
|
||||
- {"score": {"lt": 100, "gte": 50}} - score between 50 and 100
|
||||
order_by: Comma delimited list of fields to sort results by. Can order by standard agent fields like created_at, or by data fields. Data fields must be prefixed with "data.". If ordering desceding, use a " desc" suffix.
|
||||
Examples:
|
||||
- "data.name desc, created_at" - sort by name in descending order, and then by creation date
|
||||
page_size: Maximum number of items to return per page. Defaults to 10.
|
||||
offset: Number of items to skip from the beginning. Defaults to 0.
|
||||
include_total: Whether to include the total count in the response. Defaults to False to improve performance. It's recommended to only request on the first page.
|
||||
"""
|
||||
raw = await self.client.beta.search_agent_data_api_v_1_beta_agent_data_search_post(
|
||||
agent_slug=self.agent_url_id,
|
||||
collection=self.collection,
|
||||
filter=filter,
|
||||
order_by=order_by,
|
||||
offset=offset,
|
||||
page_size=page_size,
|
||||
include_total=include_total,
|
||||
)
|
||||
return TypedAgentDataItems(
|
||||
items=[
|
||||
TypedAgentData.from_raw(item, validator=self.type) for item in raw.items
|
||||
],
|
||||
has_more=raw.next_page_token is not None,
|
||||
total=raw.total_size,
|
||||
)
|
||||
|
||||
@agent_data_retry
|
||||
async def aggregate(
|
||||
self,
|
||||
filter: Optional[Dict[str, Dict[ComparisonOperator, Any]]] = None,
|
||||
group_by: Optional[List[str]] = None,
|
||||
count: Optional[bool] = None,
|
||||
first: Optional[bool] = None,
|
||||
order_by: Optional[str] = None,
|
||||
offset: Optional[int] = None,
|
||||
page_size: Optional[int] = None,
|
||||
) -> TypedAggregateGroupItems[AgentDataT]:
|
||||
"""
|
||||
Aggregate agent data into groups according to the group_by fields.
|
||||
Args:
|
||||
filter: Filter conditions to apply to the search. Dict mapping field names to FilterOperation objects. Filters only by data fields
|
||||
See search for more details on filtering.
|
||||
group_by: List of fields to group by. Groups strictly by equality. Can only group by data fields.
|
||||
Examples:
|
||||
- ["name"] - group by name
|
||||
- ["name", "age"] - group by name and age
|
||||
count: Whether to include the count of items in each group.
|
||||
first: Whether to include the first item in each group.
|
||||
order_by: Comma delimited list of fields to sort results by. See search for more details on ordering.
|
||||
offset: Number of groups to skip from the beginning. Defaults to 0.
|
||||
page_size: Maximum number of groups to return per page.
|
||||
"""
|
||||
raw = await self.client.beta.aggregate_agent_data_api_v_1_beta_agent_data_aggregate_post(
|
||||
agent_slug=self.agent_url_id,
|
||||
collection=self.collection,
|
||||
page_size=page_size,
|
||||
filter=filter,
|
||||
order_by=order_by,
|
||||
group_by=group_by,
|
||||
count=count,
|
||||
first=first,
|
||||
offset=offset,
|
||||
)
|
||||
return TypedAggregateGroupItems(
|
||||
items=[
|
||||
TypedAggregateGroup.from_raw(item, validator=self.type)
|
||||
for item in raw.items
|
||||
],
|
||||
has_more=raw.next_page_token is not None,
|
||||
total=raw.total_size,
|
||||
)
|
||||
@@ -0,0 +1,613 @@
|
||||
"""
|
||||
Agent Data API Schema Definitions
|
||||
|
||||
This module provides typed wrappers around the raw LlamaCloud agent data API,
|
||||
enabling type-safe interactions with agent-generated structured data.
|
||||
|
||||
The agent data API serves as a persistent storage system for structured data
|
||||
produced by LlamaCloud agents (particularly extraction agents). It provides
|
||||
CRUD operations, search capabilities, filtering, and aggregation functionality
|
||||
for managing agent-generated data at scale.
|
||||
|
||||
Key Concepts:
|
||||
- Agent Slug: Unique identifier for an agent instance
|
||||
- Collection: Named grouping of data within an agent (defaults to "default"). Data within a collection should be of the same type.
|
||||
- Agent Data: Individual structured data records with metadata and timestamps
|
||||
|
||||
Example Usage:
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Person(BaseModel):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
client = AsyncAgentDataClient(
|
||||
client=async_llama_cloud,
|
||||
type=Person,
|
||||
collection="people",
|
||||
agent_url_id="my-extraction-agent-xyz"
|
||||
)
|
||||
|
||||
# Create typed data
|
||||
person = Person(name="John", age=30)
|
||||
result = await client.create_agent_data(person)
|
||||
print(result.data.name) # Type-safe access
|
||||
```
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from llama_cloud import ExtractRun
|
||||
from llama_cloud.types.agent_data import AgentData
|
||||
from llama_cloud.types.aggregate_group import AggregateGroup
|
||||
from pydantic import BaseModel, Field, ValidationError, model_validator, ConfigDict
|
||||
from typing import (
|
||||
Generic,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Dict,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
Any,
|
||||
)
|
||||
|
||||
|
||||
# Type variable for user-defined data models
|
||||
AgentDataT = TypeVar("AgentDataT", bound=BaseModel)
|
||||
|
||||
# Type variable for extracted data (can be dict or Pydantic model)
|
||||
ExtractedT = TypeVar("ExtractedT", bound=Union[BaseModel, dict])
|
||||
|
||||
# Status types for extracted data workflow
|
||||
StatusType = Union[Literal["error", "accepted", "rejected", "pending_review"], str]
|
||||
|
||||
ComparisonOperator = Dict[
|
||||
str, Dict[Literal["gt", "gte", "lt", "lte", "eq", "includes"], Any]
|
||||
]
|
||||
|
||||
|
||||
class TypedAgentData(BaseModel, Generic[AgentDataT]):
|
||||
"""
|
||||
Type-safe wrapper for agent data records.
|
||||
|
||||
This class represents a single data record stored in the agent data API,
|
||||
combining the structured data payload with metadata about when and where
|
||||
it was created.
|
||||
|
||||
Attributes:
|
||||
id: Unique identifier for this data record
|
||||
agent_url_id: Identifier of the agent that created this data
|
||||
collection: Named collection within the agent (used for organization)
|
||||
data: The actual structured data payload (typed as AgentDataT)
|
||||
created_at: Timestamp when the record was first created
|
||||
updated_at: Timestamp when the record was last modified
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Access typed data
|
||||
person_data: TypedAgentData[Person] = await client.get_agent_data(id)
|
||||
print(person_data.data.name) # Type-safe access to Person fields
|
||||
print(person_data.created_at) # Access metadata
|
||||
```
|
||||
"""
|
||||
|
||||
id: Optional[str] = Field(description="Unique identifier for this data record")
|
||||
agent_url_id: str = Field(
|
||||
description="Identifier of the agent that created this data"
|
||||
)
|
||||
collection: Optional[str] = Field(
|
||||
description="Named collection within the agent for data organization"
|
||||
)
|
||||
data: AgentDataT = Field(description="The structured data payload")
|
||||
created_at: Optional[datetime] = Field(description="When this record was created")
|
||||
updated_at: Optional[datetime] = Field(
|
||||
description="When this record was last modified"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_raw(
|
||||
cls, raw_data: AgentData, validator: Type[AgentDataT]
|
||||
) -> "TypedAgentData[AgentDataT]":
|
||||
"""
|
||||
Convert raw API response to typed agent data.
|
||||
|
||||
Args:
|
||||
raw_data: Raw agent data from the API
|
||||
validator: Pydantic model class to validate the data field
|
||||
|
||||
Returns:
|
||||
TypedAgentData instance with validated data
|
||||
"""
|
||||
data: AgentDataT = validator.model_validate(raw_data.data)
|
||||
|
||||
return cls(
|
||||
id=raw_data.id,
|
||||
agent_url_id=raw_data.agent_slug,
|
||||
collection=raw_data.collection,
|
||||
data=data,
|
||||
created_at=raw_data.created_at,
|
||||
updated_at=raw_data.updated_at,
|
||||
)
|
||||
|
||||
|
||||
class TypedAgentDataItems(BaseModel, Generic[AgentDataT]):
|
||||
"""
|
||||
Paginated collection of agent data records.
|
||||
|
||||
This class represents a page of search results from the agent data API,
|
||||
providing both the data records and pagination metadata.
|
||||
|
||||
Attributes:
|
||||
items: List of agent data records in this page
|
||||
total: Total number of records matching the query (only present if requested)
|
||||
has_more: Whether there are more records available beyond this page
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Search with pagination
|
||||
results = await client.search(
|
||||
page_size=10,
|
||||
include_total=True
|
||||
)
|
||||
|
||||
for item in results.items:
|
||||
print(item.data.name)
|
||||
|
||||
if results.has_more:
|
||||
# Load next page
|
||||
next_page = await client.search(
|
||||
page_size=10,
|
||||
offset=10
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
items: List[TypedAgentData[AgentDataT]] = Field(
|
||||
description="List of agent data records in this page"
|
||||
)
|
||||
total: Optional[int] = Field(
|
||||
description="Total number of records matching the query (only present if requested)"
|
||||
)
|
||||
has_more: bool = Field(
|
||||
description="Whether there are more records available beyond this page"
|
||||
)
|
||||
|
||||
|
||||
class FieldCitation(BaseModel):
|
||||
page: Optional[int] = Field(
|
||||
None, description="The page number that the field occurred on"
|
||||
)
|
||||
matching_text: Optional[str] = Field(
|
||||
None,
|
||||
description="The original text this field's value was derived from",
|
||||
)
|
||||
|
||||
|
||||
class ExtractedFieldMetadata(BaseModel):
|
||||
"""
|
||||
Metadata for an extracted data field, such as confidence, and citation information.
|
||||
"""
|
||||
|
||||
reasoning: Optional[str] = Field(
|
||||
None,
|
||||
description="symbol for how the citation/confidence was derived: 'INFERRED FROM TEXT', 'VERBATIM EXTRACTION'",
|
||||
)
|
||||
confidence: Optional[float] = Field(
|
||||
None,
|
||||
description="The confidence score for the field, combined with parsing confidence if applicable",
|
||||
)
|
||||
extraction_confidence: Optional[float] = Field(
|
||||
None,
|
||||
description="The confidence score for the field based on the extracted text only",
|
||||
)
|
||||
citation: Optional[List[FieldCitation]] = Field(
|
||||
None,
|
||||
description="The citation for the field, including page number and matching text",
|
||||
)
|
||||
|
||||
# Forbid unknown keys to avoid swallowing nested dicts
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
ExtractedFieldMetaDataDict = Dict[
|
||||
str, Union[ExtractedFieldMetadata, Dict[str, Any], list[Any]]
|
||||
]
|
||||
|
||||
|
||||
def parse_extracted_field_metadata(
|
||||
field_metadata: dict[str, Any],
|
||||
) -> ExtractedFieldMetaDataDict:
|
||||
return {
|
||||
k: _parse_extracted_field_metadata_recursive(v)
|
||||
for k, v in field_metadata.items()
|
||||
if k not in _METADATA_FIELDS_SIBLING_TO_LEAF
|
||||
}
|
||||
|
||||
|
||||
_METADATA_FIELDS_SIBLING_TO_LEAF = {"reasoning"}
|
||||
|
||||
|
||||
def _parse_extracted_field_metadata_recursive(
|
||||
field_value: Any,
|
||||
additional_fields: dict[str, Any] = {},
|
||||
) -> Union[ExtractedFieldMetadata, Dict[str, Any], list[Any]]:
|
||||
"""
|
||||
Parse the extracted field metadata into a dictionary of field names to field metadata.
|
||||
"""
|
||||
|
||||
if isinstance(field_value, ExtractedFieldMetadata):
|
||||
# support running this multiple times
|
||||
return field_value
|
||||
elif isinstance(field_value, dict):
|
||||
# reasoning explicitly excluded, as it is included next to subfields, for example
|
||||
# "dimensions.width" is a leaf, but there will still potentially be a "dimensions.reasoning"
|
||||
indicator_fields = {"confidence", "extraction_confidence", "citation"}
|
||||
if len(indicator_fields.intersection(field_value.keys())) > 0:
|
||||
try:
|
||||
merged = {**field_value, **additional_fields}
|
||||
allowed_fields = ExtractedFieldMetadata.model_fields.keys()
|
||||
merged = {k: v for k, v in merged.items() if k in allowed_fields}
|
||||
validated = ExtractedFieldMetadata.model_validate(merged)
|
||||
|
||||
return validated
|
||||
except ValidationError:
|
||||
pass
|
||||
additional_fields = {
|
||||
k: v
|
||||
for k, v in field_value.items()
|
||||
if k in _METADATA_FIELDS_SIBLING_TO_LEAF
|
||||
}
|
||||
return {
|
||||
k: _parse_extracted_field_metadata_recursive(v, additional_fields)
|
||||
for k, v in field_value.items()
|
||||
if k not in _METADATA_FIELDS_SIBLING_TO_LEAF
|
||||
}
|
||||
elif isinstance(field_value, list):
|
||||
return [_parse_extracted_field_metadata_recursive(item) for item in field_value]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid field value: {field_value}. Expected ExtractedFieldMetadata, dict, or list"
|
||||
)
|
||||
|
||||
|
||||
class ExtractedData(BaseModel, Generic[ExtractedT]):
|
||||
"""
|
||||
Wrapper for extracted data with workflow status tracking.
|
||||
|
||||
This class is designed for extraction workflows where data goes through
|
||||
review and approval stages. It maintains both the original extracted data
|
||||
and the current state after any modifications.
|
||||
|
||||
Attributes:
|
||||
original_data: The data as originally extracted from the source
|
||||
data: The current state of the data (may differ from original after edits)
|
||||
status: Current workflow status (in_review, accepted, rejected, error)
|
||||
confidence: Confidence scores for individual fields (if available)
|
||||
file_id: The llamacloud file ID of the file that was used to extract the data
|
||||
file_name: The name of the file that was used to extract the data
|
||||
file_hash: A content hash of the file that was used to extract the data, for de-duplication
|
||||
|
||||
Status Workflow:
|
||||
- "pending_review": Initial state, awaiting human review
|
||||
- "accepted": Data approved and ready for use
|
||||
- "rejected": Data rejected, needs re-extraction or manual fix
|
||||
- "error": Processing error occurred
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Create extracted data for review
|
||||
extracted = ExtractedData.create(
|
||||
data=person_data,
|
||||
status="pending_review",
|
||||
confidence={"name": 0.95, "age": 0.87}
|
||||
)
|
||||
|
||||
# Later, after review
|
||||
if extracted.status == "accepted":
|
||||
# Use the data
|
||||
process_person(extracted.data)
|
||||
```
|
||||
"""
|
||||
|
||||
original_data: ExtractedT = Field(
|
||||
description="The original data that was extracted from the document"
|
||||
)
|
||||
data: ExtractedT = Field(
|
||||
description="The latest state of the data. Will differ if data has been updated"
|
||||
)
|
||||
status: StatusType = Field(description="The status of the extracted data")
|
||||
overall_confidence: Optional[float] = Field(
|
||||
None,
|
||||
description="The overall confidence score for the extracted data",
|
||||
)
|
||||
field_metadata: ExtractedFieldMetaDataDict = Field(
|
||||
default_factory=dict,
|
||||
description="Page links, and perhaps eventually bounding boxes, for individual fields in the extracted data. Structure is expected to have a ",
|
||||
)
|
||||
file_id: Optional[str] = Field(
|
||||
None, description="The ID of the file that was used to extract the data"
|
||||
)
|
||||
file_name: Optional[str] = Field(
|
||||
None, description="The name of the file that was used to extract the data"
|
||||
)
|
||||
file_hash: Optional[str] = Field(
|
||||
None, description="The hash of the file that was used to extract the data"
|
||||
)
|
||||
metadata: Optional[Dict[str, Any]] = Field(
|
||||
default_factory=dict,
|
||||
description="Additional metadata about the extracted data, such as errors, tokens, etc.",
|
||||
)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def _normalize_field_metadata_on_input(cls, value: Any) -> Any:
|
||||
# Ensure any inbound representation (including JSON round-trips)
|
||||
# gets normalized so nested dicts become ExtractedFieldMetadata where appropriate.
|
||||
if (
|
||||
isinstance(value, dict)
|
||||
and "field_metadata" in value
|
||||
and isinstance(value["field_metadata"], dict)
|
||||
):
|
||||
try:
|
||||
value = {
|
||||
**value,
|
||||
"field_metadata": parse_extracted_field_metadata(
|
||||
value["field_metadata"]
|
||||
),
|
||||
}
|
||||
except Exception:
|
||||
# Let pydantic surface detailed errors later rather than swallowing completely
|
||||
pass
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
data: ExtractedT,
|
||||
status: StatusType = "pending_review",
|
||||
field_metadata: ExtractedFieldMetaDataDict = {},
|
||||
file_id: Optional[str] = None,
|
||||
file_name: Optional[str] = None,
|
||||
file_hash: Optional[str] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> "ExtractedData[ExtractedT]":
|
||||
"""
|
||||
Create a new ExtractedData instance with sensible defaults.
|
||||
|
||||
Args:
|
||||
extracted_data: The extracted data payload
|
||||
status: Initial workflow status
|
||||
field_metadata: Optional confidence scores, citations, and other metadata for fields
|
||||
file_id: The llamacloud file ID of the file that was used to extract the data
|
||||
file_name: The name of the file that was used to extract the data
|
||||
file_hash: A content hash of the file that was used to extract the data, for de-duplication
|
||||
metadata: Arbitrary additional application-specific data about the extracted data
|
||||
|
||||
Returns:
|
||||
New ExtractedData instance ready for storage
|
||||
"""
|
||||
normalized_field_metadata = parse_extracted_field_metadata(field_metadata)
|
||||
return cls(
|
||||
original_data=data,
|
||||
data=data,
|
||||
status=status,
|
||||
field_metadata=normalized_field_metadata,
|
||||
overall_confidence=calculate_overall_confidence(normalized_field_metadata),
|
||||
file_id=file_id,
|
||||
file_name=file_name,
|
||||
file_hash=file_hash,
|
||||
metadata=metadata or {},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_extraction_result(
|
||||
cls,
|
||||
result: ExtractRun,
|
||||
schema: Type[ExtractedT],
|
||||
file_hash: Optional[str] = None,
|
||||
file_name: Optional[str] = None,
|
||||
file_id: Optional[str] = None,
|
||||
status: StatusType = "pending_review",
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> "ExtractedData[ExtractedT]":
|
||||
"""
|
||||
Create an ExtractedData instance from an extraction result.
|
||||
"""
|
||||
file_id = file_id or result.file.id
|
||||
file_name = file_name or result.file.name
|
||||
|
||||
try:
|
||||
field_metadata = parse_extracted_field_metadata(
|
||||
result.extraction_metadata.get("field_metadata", {})
|
||||
)
|
||||
except ValidationError:
|
||||
field_metadata = {}
|
||||
|
||||
try:
|
||||
data = schema.model_validate(result.data) # type: ignore
|
||||
return cls.create(
|
||||
data=data,
|
||||
status=status,
|
||||
field_metadata=field_metadata,
|
||||
file_id=file_id,
|
||||
file_name=file_name,
|
||||
file_hash=file_hash,
|
||||
metadata=metadata or {},
|
||||
)
|
||||
except ValidationError as e:
|
||||
invalid_item = ExtractedData[Dict[str, Any]].create(
|
||||
data=result.data or {},
|
||||
status="error",
|
||||
field_metadata=field_metadata,
|
||||
metadata={"extraction_error": str(e), **(metadata or {})},
|
||||
file_id=file_id,
|
||||
file_name=file_name,
|
||||
file_hash=file_hash,
|
||||
)
|
||||
raise InvalidExtractionData(invalid_item) from e
|
||||
|
||||
|
||||
class InvalidExtractionData(Exception):
|
||||
"""
|
||||
Exception raised when the extracted data does not conform to the schema.
|
||||
"""
|
||||
|
||||
def __init__(self, invalid_item: ExtractedData[Dict[str, Any]]):
|
||||
self.invalid_item = invalid_item
|
||||
super().__init__("Not able to parse the extracted data, parsed invalid format")
|
||||
|
||||
|
||||
def calculate_overall_confidence(
|
||||
metadata: ExtractedFieldMetaDataDict,
|
||||
) -> Optional[float]:
|
||||
"""
|
||||
Calculate the overall confidence score for the extracted data.
|
||||
"""
|
||||
numerator, denominator = _calculate_overall_confidence_recursive(metadata)
|
||||
if denominator == 0:
|
||||
return None
|
||||
return numerator / denominator
|
||||
|
||||
|
||||
def _calculate_overall_confidence_recursive(
|
||||
confidence: Union[ExtractedFieldMetadata, Dict[str, Any], list[Any]],
|
||||
) -> tuple[float, int]:
|
||||
"""
|
||||
Calculate the overall confidence score for the extracted data.
|
||||
"""
|
||||
if isinstance(confidence, ExtractedFieldMetadata):
|
||||
if confidence.confidence is not None:
|
||||
return confidence.confidence, 1
|
||||
else:
|
||||
return 0, 0
|
||||
if isinstance(confidence, dict):
|
||||
numerator: float = 0
|
||||
denominator: int = 0
|
||||
for value in confidence.values():
|
||||
num, den = _calculate_overall_confidence_recursive(value)
|
||||
numerator += num
|
||||
denominator += den
|
||||
return numerator, denominator
|
||||
elif isinstance(confidence, list):
|
||||
numerator = 0
|
||||
denominator = 0
|
||||
for value in confidence:
|
||||
num, den = _calculate_overall_confidence_recursive(value)
|
||||
numerator += num
|
||||
denominator += den
|
||||
return numerator, denominator
|
||||
else:
|
||||
return 0, 0
|
||||
|
||||
|
||||
class TypedAggregateGroup(BaseModel, Generic[AgentDataT]):
|
||||
"""
|
||||
Represents a group of agent data records aggregated by common field values.
|
||||
|
||||
This class is used for grouping and analyzing agent data based on shared
|
||||
characteristics. It's particularly useful for generating summaries and
|
||||
statistics across large datasets.
|
||||
|
||||
Attributes:
|
||||
group_key: The field values that define this group
|
||||
count: Number of records in this group (if count aggregation was requested)
|
||||
first_item: Representative data record from this group (if requested)
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Group by age range
|
||||
groups = await client.aggregate_agent_data(
|
||||
group_by=["age_range"],
|
||||
count=True,
|
||||
first=True
|
||||
)
|
||||
|
||||
for group in groups.items:
|
||||
print(f"Age range {group.group_key['age_range']}: {group.count} people")
|
||||
if group.first_item:
|
||||
print(f"Example: {group.first_item.name}")
|
||||
```
|
||||
"""
|
||||
|
||||
group_key: Dict[str, Any] = Field(
|
||||
description="The field values that define this group"
|
||||
)
|
||||
count: Optional[int] = Field(
|
||||
description="Number of records in this group (if count aggregation was requested)"
|
||||
)
|
||||
first_item: Optional[AgentDataT] = Field(
|
||||
description="Representative data record from this group (if requested)"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_raw(
|
||||
cls, raw_data: AggregateGroup, validator: Type[AgentDataT]
|
||||
) -> "TypedAggregateGroup[AgentDataT]":
|
||||
"""
|
||||
Convert raw API response to typed aggregate group.
|
||||
|
||||
Args:
|
||||
raw_data: Raw aggregate group from the API
|
||||
validator: Pydantic model class to validate the first_item field
|
||||
|
||||
Returns:
|
||||
TypedAggregateGroup instance with validated first_item
|
||||
"""
|
||||
first_item: Optional[AgentDataT] = raw_data.first_item
|
||||
if first_item is not None:
|
||||
first_item = validator.model_validate(first_item)
|
||||
|
||||
return cls(
|
||||
group_key=raw_data.group_key,
|
||||
count=raw_data.count,
|
||||
first_item=first_item,
|
||||
)
|
||||
|
||||
|
||||
class TypedAggregateGroupItems(BaseModel, Generic[AgentDataT]):
|
||||
"""
|
||||
Paginated collection of aggregate groups.
|
||||
|
||||
This class represents a page of aggregation results from the agent data API,
|
||||
providing both the grouped data and pagination metadata.
|
||||
|
||||
Attributes:
|
||||
items: List of aggregate groups in this page
|
||||
total: Total number of groups matching the query (only present if requested)
|
||||
has_more: Whether there are more groups available beyond this page
|
||||
|
||||
Example:
|
||||
```python
|
||||
# Get first page of groups
|
||||
results = await client.aggregate_agent_data(
|
||||
group_by=["department"],
|
||||
count=True,
|
||||
page_size=20
|
||||
)
|
||||
|
||||
for group in results.items:
|
||||
dept = group.group_key["department"]
|
||||
print(f"{dept}: {group.count} employees")
|
||||
|
||||
# Load more if needed
|
||||
if results.has_more:
|
||||
next_page = await client.aggregate_agent_data(
|
||||
group_by=["department"],
|
||||
count=True,
|
||||
page_size=20,
|
||||
offset=20
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
items: List[TypedAggregateGroup[AgentDataT]] = Field(
|
||||
description="List of aggregate groups in this page"
|
||||
)
|
||||
total: Optional[int] = Field(
|
||||
description="Total number of groups matching the query (only present if requested)"
|
||||
)
|
||||
has_more: bool = Field(
|
||||
description="Whether there are more groups available beyond this page"
|
||||
)
|
||||
@@ -0,0 +1,187 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
from llama_cloud.client import AsyncLlamaCloud
|
||||
from llama_cloud.types import (
|
||||
ClassifierRule,
|
||||
ClassifyJobResults,
|
||||
ClassifyParsingConfiguration,
|
||||
StatusEnum,
|
||||
ClassifyJobWithStatus,
|
||||
File,
|
||||
)
|
||||
from llama_cloud.resources.classifier.client import OMIT
|
||||
from llama_cloud_services.files.client import FileClient
|
||||
from llama_cloud_services.constants import POLLING_TIMEOUT_SECONDS
|
||||
from llama_cloud_services.utils import is_terminal_status, augment_async_errors
|
||||
from llama_index.core.async_utils import DEFAULT_NUM_WORKERS, run_jobs
|
||||
|
||||
|
||||
class ClassificationOutput(BaseModel):
|
||||
file_id: str
|
||||
classification: str
|
||||
|
||||
|
||||
class ClassifyClient:
|
||||
"""
|
||||
Experimental - Client for interacting with the LlamaCloud Classifier API.
|
||||
The Classification API is currently in beta and may change in the future without notice.
|
||||
|
||||
Args:
|
||||
client: The LlamaCloud client to use.
|
||||
project_id: The project ID to use.
|
||||
organization_id: The organization ID to use.
|
||||
polling_interval: The interval to poll for job completion in seconds.
|
||||
polling_timeout: The timeout for the job to complete in seconds.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: AsyncLlamaCloud,
|
||||
project_id: Optional[str] = None,
|
||||
organization_id: Optional[str] = None,
|
||||
polling_interval: float = 1.0,
|
||||
polling_timeout: float = POLLING_TIMEOUT_SECONDS,
|
||||
):
|
||||
self.client = client
|
||||
self.project_id = project_id
|
||||
self.organization_id = organization_id
|
||||
self.polling_interval = polling_interval
|
||||
self.file_client = FileClient(client, project_id, organization_id)
|
||||
self.polling_timeout = polling_timeout
|
||||
|
||||
async def aclassify_file_ids(
|
||||
self,
|
||||
rules: list[ClassifierRule],
|
||||
file_ids: list[str],
|
||||
parsing_configuration: Optional[ClassifyParsingConfiguration] = None,
|
||||
raise_on_error: bool = True,
|
||||
) -> ClassifyJobResults:
|
||||
"""
|
||||
Classify a list of files by their IDs.
|
||||
Note that even if a job fails, some of the files may have been classified successfully.
|
||||
In this case, you may want to set raise_on_error to False and check the results for successful classifications.
|
||||
|
||||
Args:
|
||||
rules: The rules to use for classification.
|
||||
file_ids: The IDs of the files to classify.
|
||||
parsing_configuration: The parsing configuration to use for classification.
|
||||
raise_on_error: Whether to raise an error if the classification job fails.
|
||||
|
||||
Returns:
|
||||
The results of the classification job.
|
||||
"""
|
||||
classify_job = await self.client.classifier.create_classify_job(
|
||||
rules=rules,
|
||||
file_ids=file_ids,
|
||||
parsing_configuration=parsing_configuration or OMIT,
|
||||
project_id=self.project_id,
|
||||
organization_id=self.organization_id,
|
||||
)
|
||||
|
||||
classify_job_with_status = await self._wait_for_job_completion(classify_job.id)
|
||||
|
||||
if raise_on_error and classify_job_with_status.status == StatusEnum.ERROR:
|
||||
raise ValueError(
|
||||
f"Error classifying files under job ID {classify_job_with_status.id}"
|
||||
)
|
||||
|
||||
results = await self.client.classifier.get_classification_job_results(
|
||||
classify_job_with_status.id,
|
||||
project_id=self.project_id,
|
||||
organization_id=self.organization_id,
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
def classify_file_ids(
|
||||
self,
|
||||
rules: list[ClassifierRule],
|
||||
file_ids: list[str],
|
||||
parsing_configuration: Optional[ClassifyParsingConfiguration] = None,
|
||||
raise_on_error: bool = True,
|
||||
) -> ClassifyJobResults:
|
||||
with augment_async_errors():
|
||||
return asyncio.run(
|
||||
self.aclassify_file_ids(
|
||||
rules, file_ids, parsing_configuration, raise_on_error
|
||||
)
|
||||
)
|
||||
|
||||
async def aclassify_file_path(
|
||||
self,
|
||||
rules: list[ClassifierRule],
|
||||
file_input_path: str,
|
||||
parsing_configuration: Optional[ClassifyParsingConfiguration] = None,
|
||||
raise_on_error: bool = True,
|
||||
) -> ClassifyJobResults:
|
||||
file = await self.file_client.upload_file(file_input_path)
|
||||
return await self.aclassify_file_ids(
|
||||
rules, [file.id], parsing_configuration, raise_on_error
|
||||
)
|
||||
|
||||
def classify_file_path(
|
||||
self,
|
||||
rules: list[ClassifierRule],
|
||||
file_input_path: str,
|
||||
parsing_configuration: Optional[ClassifyParsingConfiguration] = None,
|
||||
raise_on_error: bool = True,
|
||||
) -> ClassifyJobResults:
|
||||
with augment_async_errors():
|
||||
return asyncio.run(
|
||||
self.aclassify_file_path(
|
||||
rules, file_input_path, parsing_configuration, raise_on_error
|
||||
)
|
||||
)
|
||||
|
||||
async def aclassify_file_paths(
|
||||
self,
|
||||
rules: list[ClassifierRule],
|
||||
file_input_paths: list[str],
|
||||
parsing_configuration: Optional[ClassifyParsingConfiguration] = None,
|
||||
raise_on_error: bool = True,
|
||||
workers: int = DEFAULT_NUM_WORKERS,
|
||||
show_progress: bool = False,
|
||||
) -> ClassifyJobResults:
|
||||
coroutines = [self.file_client.upload_file(path) for path in file_input_paths]
|
||||
files: list[File] = await run_jobs(
|
||||
coroutines,
|
||||
show_progress=show_progress,
|
||||
workers=workers,
|
||||
desc="Uploading files for classification",
|
||||
)
|
||||
return await self.aclassify_file_ids(
|
||||
rules, [file.id for file in files], parsing_configuration, raise_on_error
|
||||
)
|
||||
|
||||
def classify_file_paths(
|
||||
self,
|
||||
rules: list[ClassifierRule],
|
||||
file_input_paths: list[str],
|
||||
parsing_configuration: Optional[ClassifyParsingConfiguration] = None,
|
||||
raise_on_error: bool = True,
|
||||
) -> ClassifyJobResults:
|
||||
with augment_async_errors():
|
||||
return asyncio.run(
|
||||
self.aclassify_file_paths(
|
||||
rules, file_input_paths, parsing_configuration, raise_on_error
|
||||
)
|
||||
)
|
||||
|
||||
async def _wait_for_job_completion(self, job_id: str) -> ClassifyJobWithStatus:
|
||||
job = await self.client.classifier.get_classify_job(
|
||||
job_id, project_id=self.project_id, organization_id=self.organization_id
|
||||
)
|
||||
start_time = time.time()
|
||||
while not is_terminal_status(job.status):
|
||||
polling_duration = time.time() - start_time
|
||||
if polling_duration > self.polling_timeout:
|
||||
raise TimeoutError(
|
||||
f"Job {job_id} timed out after {polling_duration} seconds"
|
||||
)
|
||||
await asyncio.sleep(self.polling_interval)
|
||||
job = await self.client.classifier.get_classify_job(
|
||||
job_id, project_id=self.project_id, organization_id=self.organization_id
|
||||
)
|
||||
return job
|
||||
@@ -1 +1,2 @@
|
||||
EU_BASE_URL = "https://api.cloud.eu.llamaindex.ai"
|
||||
POLLING_TIMEOUT_SECONDS = 300.0
|
||||
@@ -0,0 +1,17 @@
|
||||
from llama_cloud_services.extract.extract import (
|
||||
LlamaExtract,
|
||||
ExtractConfig,
|
||||
ExtractionAgent,
|
||||
SourceText,
|
||||
ExtractTarget,
|
||||
ExtractMode,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"LlamaExtract",
|
||||
"ExtractionAgent",
|
||||
"SourceText",
|
||||
"ExtractConfig",
|
||||
"ExtractTarget",
|
||||
"ExtractMode",
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user