mirror of
https://github.com/run-llama/llama_cloud_services.git
synced 2026-07-19 16:43:32 -04:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47cb887fbc | |||
| 8cf2930320 | |||
| 5edf5f914a | |||
| 22e4975cb2 | |||
| bc2f04379b | |||
| f9f951d5d8 | |||
| 355129fea5 | |||
| d9aed80ded | |||
| c07d2d70a8 | |||
| ed6937a5a9 | |||
| 34c15932a3 | |||
| b18ea96d11 | |||
| 196ab827f5 | |||
| ba4cb4d5e9 | |||
| 58d883b825 | |||
| 5fc5ebfc6c | |||
| fe3e20fd53 | |||
| e7e59459ab | |||
| f4d7c84e19 | |||
| 9050a346e4 | |||
| 9690ccf4ea | |||
| 97745f0f1c | |||
| 61a696b9db | |||
| 3e01adaf0e |
@@ -19,8 +19,6 @@ jobs:
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Lint - Python
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -29,7 +29,18 @@ jobs:
|
||||
- name: Set up Python
|
||||
run: uv python install ${{ matrix.python-version }}
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- 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 linter
|
||||
shell: bash
|
||||
working-directory: py
|
||||
run: uv run -- pre-commit run -a
|
||||
# the js checks are run roundaboutly through lint-staged, and -a doesn't run it. Run them directly.
|
||||
- run: pnpm -w --filter llama-cloud-services run lint
|
||||
- run: pnpm -w --filter llama-cloud-services run format:check
|
||||
@@ -1,37 +0,0 @@
|
||||
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@v5
|
||||
- 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
|
||||
@@ -13,8 +13,6 @@ jobs:
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -49,6 +47,6 @@ jobs:
|
||||
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"
|
||||
name: Release ${{ github.ref_name }} - LlamaCloud Services TS
|
||||
generateReleaseNotes: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Lint - TypeScript
|
||||
name: Test - TypeScript
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -23,17 +23,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
run: pnpm -r install --no-frozen-lockfile
|
||||
- name: Build package
|
||||
run: pnpm --filter llama-cloud-services build
|
||||
- name: Run Tests
|
||||
working-directory: ts/llama_cloud_services/
|
||||
run: pnpm test
|
||||
|
||||
@@ -29,7 +29,7 @@ repos:
|
||||
- id: black-jupyter
|
||||
name: black-src
|
||||
alias: black
|
||||
exclude: ".*uv.lock"
|
||||
exclude: ".*uv.lock|examples/extract/solar_panel_e2e_comparison.ipynb"
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.0.1
|
||||
hooks:
|
||||
@@ -60,11 +60,13 @@ repos:
|
||||
additional_dependencies: [black==23.10.1]
|
||||
# Using PEP 8's line length in docs prevents excess left/right scrolling
|
||||
args: [--line-length=79]
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.3
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: prettier
|
||||
exclude: ^(uv.lock|ts/llama_cloud_services/pnpm-lock.yaml|ts/e2e-tests)
|
||||
- id: lint-staged
|
||||
name: Run lint-staged for TS files
|
||||
entry: pnpm -w exec lint-staged
|
||||
language: system
|
||||
pass_filenames: false
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.6
|
||||
hooks:
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ versions need to be kept consistent to sidecar it with `llama_cloud_services`. B
|
||||
|
||||
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`.
|
||||
Once the change is merged, push a tag `git tag -a v0.x.x -m 0.x.x` and `git push origin v0.x.x`.
|
||||
|
||||
This tagging step can be done with `./scripts/version-bump tag`.
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ This repository contains the code for hand-written SDKs and clients for interact
|
||||
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.
|
||||
|
||||
@@ -28,13 +27,11 @@ Then, you can use the services in your code:
|
||||
```python
|
||||
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"
|
||||
@@ -44,7 +41,6 @@ index = LlamaCloudIndex(
|
||||
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)
|
||||
|
||||
@@ -57,13 +53,11 @@ You can also create your API key in the EU region [here](https://cloud.eu.llamai
|
||||
```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",
|
||||
|
||||
+1
-1
@@ -4,6 +4,6 @@ In this folder you will find several python notebooks that contain examples rega
|
||||
|
||||
- [LlamaParse](./parse/)
|
||||
- [LlamaExtract](./extract/)
|
||||
- [LlamaReport](./report/)
|
||||
- [LlamaCloudIndex](./index/)
|
||||
|
||||
Follow the instructions in each notebook to get started!
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"source": [
|
||||
"# Extraction and Analysis over a Fidelity Multi-Fund Annual Report\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services-demo/blob/main/examples/extract/asset_manager_fund_analysis.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/extract/asset_manager_fund_analysis.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"\n",
|
||||
"In this notebook we show you how to create an agentic document workflow over a complex document that contains annual reports for multiple funds - each fund reports financials in a standardized reporting structure, and it's all consolidated in the same document.\n",
|
||||
"\n",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"source": [
|
||||
"# Automotive Equity Research: A Multi-Step Agentic Workflow\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services-demo/blob/main/examples/extract/automotive_sector_analysis.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/extract/automotive_sector_analysis.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"\n",
|
||||
"This notebook demonstrates an end‑to‑end agentic workflow using LlamaExtract and the LlamaIndex event‑driven workflow framework for automotive sector analysis.\n",
|
||||
"\n",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
"source": [
|
||||
"# Dynamic Section Retrieval with LlamaParse\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services-demo/blob/main/examples/parse/advanced_rag/dynamic_section_retrieval.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/parse/advanced_rag/dynamic_section_retrieval.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"\n",
|
||||
"This notebook showcases a concept called \"dynamic section retrieval\".\n",
|
||||
"\n",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"source": [
|
||||
"# Advanced RAG with LlamaParse\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_parse/blob/main/examples/demo_advanced.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_parse/blob/main/examples/parse/demo_advanced.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"\n",
|
||||
"This notebook is a complete walkthrough for using LlamaParse with advanced indexing/retrieval techniques in LlamaIndex over the Apple 10K Filing. \n",
|
||||
"\n",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"source": [
|
||||
"# RAG with Excel Spreadsheet using LlamaPrase\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/demo_excel.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/parse/demo_excel.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 using LlamaParse with Excel Spreadsheet.\n",
|
||||
"\n",
|
||||
@@ -43,7 +43,7 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"api_key = \"llx-jwAQZL8T38onyL9hKBOXyRtnuCU0Fk3z7tmDhIT3L0GEfohJ\" # get from cloud.llamaindex.ai"
|
||||
"api_key = \"llx-...\" # get from cloud.llamaindex.ai"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"source": [
|
||||
"# Download Charts\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/demo_get_charts.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/parse/demo_get_charts.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"\n",
|
||||
"This notebook demonstrates how to download charts from a document using the result object.\n",
|
||||
"\n",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"source": [
|
||||
"# LlamaParse - Fast checking Insurance Contract for Coverage\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/demo_insurance.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/parse/demo_insurance.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"\n",
|
||||
"In this notebook we will look at how LlamaParse can be used to extract structured coverage information from an insurance policy.\n",
|
||||
"\n",
|
||||
@@ -36,7 +36,7 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Download an insurance policy fron IRDAI\n",
|
||||
"## Download an insurance policy from IRDAI\n",
|
||||
"\n",
|
||||
"The Insurance Regulatory and Development Authority of India (IRDAI) maintains a great resource: https://policyholder.gov.in/web/guest/non-life-insurance-products where all insurance policies available in India are publicly available for download! Let's download a complex health insurance policy as an example."
|
||||
]
|
||||
@@ -228,11 +228,11 @@
|
||||
" result_type=\"markdown\",\n",
|
||||
" system_prompt_append=\"\"\"\n",
|
||||
"This document is an insurance policy.\n",
|
||||
"When a benefits/coverage/exlusion is describe in the document ammend to it add a text in the follwing benefits string format (where coverage could be an exclusion).\n",
|
||||
"When a benefits/coverage/exlusion is describe in the document amend to it add a text in the following benefits string format (where coverage could be an exclusion).\n",
|
||||
"\n",
|
||||
"For {nameofrisk} and in this condition {whenDoesThecoverageApply} the coverage is {coverageDescription}. \n",
|
||||
" \n",
|
||||
"If the document contain a benefits TABLE that describe coverage amounts, do not ouput it as a table, but instead as a list of benefits string.\n",
|
||||
"If the document contain a benefits TABLE that describe coverage amounts, do not output it as a table, but instead as a list of benefits string.\n",
|
||||
" \n",
|
||||
"\"\"\",\n",
|
||||
").aparse(\"./policy.pdf\")\n",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"source": [
|
||||
"# LlamaParse `JobResult` Tour\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/demo_json.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/parse/demo_json.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"\n",
|
||||
"The `JobResult` object is the main object returned by the LlamaParse API. It contains all the information about the job, including the parsed data, metadata, and any errors.\n",
|
||||
"\n",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"\n",
|
||||
"LlamaParse supports users to specify a `language` parameter before uploading documents, giving users better OCR capabilities over non-English PDFs, parsing images into more accurate representations.\n",
|
||||
"\n",
|
||||
"You can specify 80+ different languages: see this file for a full list of supported languages: https://github.com/run-llama/llama_cloud_services/blob/main/llama_parse/base.py.\n",
|
||||
"You can specify 80+ different languages: see this file for a full list of supported languages: https://github.com/run-llama/llama_cloud_services/blob/main/py/llama_cloud_services/parse/base.py.\n",
|
||||
"\n",
|
||||
"This notebook shows a demo of this in action. \n",
|
||||
"\n",
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
" adaptive_long_table=True,\n",
|
||||
" outlined_table_extraction=True,\n",
|
||||
" output_tables_as_HTML=True,\n",
|
||||
" api_key=\"llx-jwAQZL8T38onyL9hKBOXyRtnuCU0Fk3z7tmDhIT3L0GEfohJ\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"result = await parser.aparse(\"./dcf_template.xlsx\")\n",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/excel/o1_excel_rag.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/parse/excel/o1_excel_rag.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -740,7 +740,7 @@
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this example, these pages aren't going to be that different when parsed, but we can verify which pages triggered auto-made by looking at the [JSON output](https://github.com/run-llama/llama_cloud_services/blob/main/examples/demo_json_tour.ipynb) of LlamaParse:"
|
||||
"In this example, these pages aren't going to be that different when parsed, but we can verify which pages triggered auto-made by looking at the [JSON output](https://github.com/run-llama/llama_cloud_services/blob/main/examples/parse/demo_json_tour.ipynb) of LlamaParse:"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,762 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Report Generation with LlamaReport\n",
|
||||
"\n",
|
||||
"In this notebook, we'll walk through the basic process of generating a report with LlamaReport, and highlight some of the key features of the library.\n",
|
||||
"\n",
|
||||
"TLDR:\n",
|
||||
"1. Download source data to use as knowledge base for the report\n",
|
||||
"2. Kick off report generation with a template\n",
|
||||
"3. Get the plan and review/accept/reject suggestions\n",
|
||||
"4. Get the final report\n",
|
||||
"5. Review/accept/reject suggestions to edit the final report\n",
|
||||
"6. Print the final report"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Download Source Data\n",
|
||||
"\n",
|
||||
"Here, we download the `Attention is All You Need` paper as a PDF.\n",
|
||||
"\n",
|
||||
"LlamaReport currently supports up to 5 files as input, and essentially any file type that can be parsed by LlamaParse.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget \"https://arxiv.org/pdf/1706.03762.pdf\" -O \"./attention.pdf\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Kick off Report Generation\n",
|
||||
"\n",
|
||||
"Here, we kick off report generation with a template.\n",
|
||||
"\n",
|
||||
"The template can either be a string or a file path, but here we'll use a string.\n",
|
||||
"\n",
|
||||
"In our experiments, anything works as a template, but some general guidelines:\n",
|
||||
"\n",
|
||||
"- Use markdown formatting + instructions in each section to guide the report generation\n",
|
||||
"- If using an existing file as a template, provide extra instructions to guide the report generation\n",
|
||||
"\n",
|
||||
"**NOTE:** Since we are in a notebook, we will use async functions and `await` throughout. Synchronous methods that work without `await` are available by just removing the `a` from the method name and removing the `await` keyword."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaReport\n",
|
||||
"\n",
|
||||
"llama_report = LlamaReport(\n",
|
||||
" api_key=\"llx-...\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"report_client = await llama_report.acreate_report(\n",
|
||||
" name=\"my_cool_report_on_attention\",\n",
|
||||
" # can pass in file paths or bytes\n",
|
||||
" input_files=[\"./attention.pdf\"],\n",
|
||||
" template_text=\"\"\"\\\n",
|
||||
"# [Some title]\\n\\n\n",
|
||||
"## TLDR\\n\n",
|
||||
"A quick summary of the paper.\\n\\n\n",
|
||||
"## Details\\n\n",
|
||||
"More details about the paper, possibly more than one section here.\\n\n",
|
||||
"\"\"\",\n",
|
||||
" # optional additional instructions for the report generation\n",
|
||||
" # template_instructions=None,\n",
|
||||
" # optional file path to an existing template instead of template_text\n",
|
||||
" # template_file=None,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The returned `ReportClient` object is used to interact with the report generation process for this specific report."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Report(id=0a394b33-1a3e-463c-b5cb-7ff8ab827d0a, name=my_cool_report_on_attention)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(report_client)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Get the plan\n",
|
||||
"\n",
|
||||
"The first phases of report generation involve ingesting the source data and generating a plan.\n",
|
||||
"\n",
|
||||
"The plan is a list of instructions for the report generation, and can be reviewed/accepted/rejected by the user.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plan = await report_client.await_for_plan(\n",
|
||||
" timeout=10000,\n",
|
||||
" poll_interval=10,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"# {title}\n",
|
||||
"[ReportQuery(field='title', prompt='Generate a clear and concise title for this paper about the Transformer model and attention mechanisms', context='The paper discusses the Transformer architecture for sequence transduction using attention mechanisms, focusing on machine translation applications')]\n",
|
||||
"==================\n",
|
||||
"## TLDR\n",
|
||||
"\n",
|
||||
"{tldr_content}\n",
|
||||
"[ReportQuery(field='tldr_content', prompt='Write a brief, clear summary of the key points about the Transformer model', context='Focus on the main innovations: attention mechanisms, efficiency improvements, and state-of-the-art results in machine translation')]\n",
|
||||
"==================\n",
|
||||
"## Details\n",
|
||||
"\n",
|
||||
"{details_content}\n",
|
||||
"[ReportQuery(field='details_content', prompt='Provide detailed information about the Transformer model architecture and its applications', context='Include information about:\\n- The attention mechanism implementation\\n- Advantages over recurrent and convolutional models\\n- Performance in machine translation tasks\\n- Training efficiency improvements')]\n",
|
||||
"==================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for plan_block in plan.blocks:\n",
|
||||
" print(plan_block.block.template)\n",
|
||||
" print(plan_block.queries)\n",
|
||||
" print(\"==================\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"With the plan, we can either use it to kick off generation of the final report, or we can edit the plan and adjust it as needed.\n",
|
||||
"\n",
|
||||
"While we could manually edit the objects here and use `await report_client.aupdate_plan(action=\"edit\", updated_plan=plan)`, we can also use `LlamaReport` to agentically edit the plan."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"suggestions = await report_client.asuggest_edits(\n",
|
||||
" \"Can you split the details section into two sections?\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Justification for change: \n",
|
||||
"I'll help you break down the details section into two distinct parts - one focusing on the architecture and another on the practical applications and performance. This will make the content more organized and easier to follow. The original block at index 2 will be replaced with these two new sections.\n",
|
||||
"\n",
|
||||
"Proposed changes:\n",
|
||||
"\n",
|
||||
"## Architecture Details\n",
|
||||
"\n",
|
||||
"{architecture_content}\n",
|
||||
"\n",
|
||||
"[ReportQuery(field='architecture_content', prompt='Describe the technical details of the Transformer model architecture', context='Focus on:\\n- Core components of the Transformer architecture\\n- Self-attention mechanism implementation\\n- Multi-head attention details\\n- Position encoding approach\\n- Feed-forward network structure')]\n",
|
||||
"==================\n",
|
||||
"\n",
|
||||
"## Performance and Applications\n",
|
||||
"\n",
|
||||
"{applications_content}\n",
|
||||
"\n",
|
||||
"[ReportQuery(field='applications_content', prompt='Explain the practical applications and performance advantages of the Transformer model', context='Cover:\\n- Comparison with RNN and CNN models\\n- Machine translation results and benchmarks\\n- Training efficiency improvements\\n- Real-world applications and use cases\\n- Scalability benefits')]\n",
|
||||
"==================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for suggestion in suggestions:\n",
|
||||
" print(\"Justification for change:\", suggestion.justification)\n",
|
||||
" print(\"Proposed changes:\")\n",
|
||||
" for plan_block in suggestion.blocks:\n",
|
||||
" print(plan_block.block.template)\n",
|
||||
" print(plan_block.queries)\n",
|
||||
" print(\"==================\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This looks pretty good! We can also use the client to automatically accept and apply, or reject, these suggestions.\n",
|
||||
"\n",
|
||||
"This will (locally) keep track of the history of changes, so that future suggestions can be based on the previous changes."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for suggestion in suggestions:\n",
|
||||
" await report_client.aaccept_edit(suggestion)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"What effect did that have on the tracked local history? Let's see!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[EditAction(block_idx=2, old_content='## Details\\n\\n{details_content}\\n\\nField: details_content, Prompt: Provide detailed information about the Transformer model architecture and its applications, Context: Include information about:\\n- The attention mechanism implementation\\n- Advantages over recurrent and convolutional models\\n- Performance in machine translation tasks\\n- Training efficiency improvements\\nDepends on: none', new_content='\\n## Architecture Details\\n\\n{architecture_content}\\n\\n\\nField: architecture_content, Prompt: Describe the technical details of the Transformer model architecture, Context: Focus on:\\n- Core components of the Transformer architecture\\n- Self-attention mechanism implementation\\n- Multi-head attention details\\n- Position encoding approach\\n- Feed-forward network structure\\nDepends on: none', action='approved', timestamp=datetime.datetime(2025, 2, 4, 20, 59, 55, 773558)),\n",
|
||||
" EditAction(block_idx=3, old_content='[No old content]', new_content='\\n## Performance and Applications\\n\\n{applications_content}\\n\\n\\nField: applications_content, Prompt: Explain the practical applications and performance advantages of the Transformer model, Context: Cover:\\n- Comparison with RNN and CNN models\\n- Machine translation results and benchmarks\\n- Training efficiency improvements\\n- Real-world applications and use cases\\n- Scalability benefits\\nDepends on: previous', action='approved', timestamp=datetime.datetime(2025, 2, 4, 20, 59, 55, 773687))]"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"report_client.edit_history"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[Message(role=<MessageRole.USER: 'user'>, content='Can you split the details section into two sections?', timestamp=datetime.datetime(2025, 2, 4, 20, 59, 47, 754848)),\n",
|
||||
" Message(role=<MessageRole.ASSISTANT: 'assistant'>, content=\"\\nI'll help you break down the details section into two distinct parts - one focusing on the architecture and another on the practical applications and performance. This will make the content more organized and easier to follow. The original block at index 2 will be replaced with these two new sections.\\n\", timestamp=datetime.datetime(2025, 2, 4, 20, 59, 55, 482070))]"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"report_client.chat_history"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"These two items are used to provide context for future suggestions! You can always clear this, or provide your own history."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# report_client.suggest_edits(\"....\", chat_history=[{\"role\": \"user\", \"content\": \"...\"}, ...])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Get the final report\n",
|
||||
"\n",
|
||||
"Now that we have a plan, we can kick off generation of the final report."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# kicks off report generation\n",
|
||||
"await report_client.aupdate_plan(action=\"approve\")\n",
|
||||
"\n",
|
||||
"# waits for report generation to complete\n",
|
||||
"report = await report_client.await_completion(\n",
|
||||
" timeout=10000,\n",
|
||||
" poll_interval=10,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"# Attention Is All You Need: A Pure Attention-Based Architecture for Neural Machine Translation\n",
|
||||
"\n",
|
||||
"## TLDR\n",
|
||||
"\n",
|
||||
"The Transformer introduced a revolutionary architecture that relies entirely on attention mechanisms, eliminating the need for recurrence or convolution in sequence processing. Its key innovations include multi-head self-attention for parallel processing of input sequences, scaled dot-product attention for efficient computation, and positional encodings for sequence order awareness. The model achieved breakthrough results in machine translation (28.4 BLEU on English-to-German, 41.8 BLEU on English-to-French) while requiring significantly less training time than previous approaches, training in 3.5 days on 8 GPUs. This architecture demonstrated that attention mechanisms alone are sufficient for state-of-the-art sequence modeling, setting a new direction for natural language processing.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Architecture Details\n",
|
||||
"\n",
|
||||
"The Transformer architecture represents a groundbreaking approach to sequence processing, built entirely on attention mechanisms without recurrence or convolution. Here are its key technical details:\n",
|
||||
"\n",
|
||||
"Core Components:\n",
|
||||
"- Encoder-decoder architecture with stacked self-attention and point-wise feed-forward layers\n",
|
||||
"- Each layer contains two main sub-layers: multi-head self-attention mechanism and position-wise feed-forward network\n",
|
||||
"- Layer normalization and residual connections between sub-layers\n",
|
||||
"- No recurrent or convolutional elements, enabling parallel processing\n",
|
||||
"\n",
|
||||
"Self-Attention Mechanism:\n",
|
||||
"- Processes relationships between all positions in a sequence simultaneously\n",
|
||||
"- Computes attention weights using queries, keys, and values derived from input representations\n",
|
||||
"- Implements scaled dot-product attention to prevent gradient issues with large input dimensions\n",
|
||||
"- Allows direct modeling of dependencies regardless of positional distance\n",
|
||||
"- Uses masking in decoder to prevent leftward information flow and maintain auto-regressive property\n",
|
||||
"\n",
|
||||
"Multi-Head Attention:\n",
|
||||
"- Employs multiple attention heads operating in parallel\n",
|
||||
"- Each head processes information in different representation subspaces\n",
|
||||
"- Three types of attention applications:\n",
|
||||
" 1. Encoder self-attention (all positions attend to each other)\n",
|
||||
" 2. Decoder self-attention (each position attends to previous positions)\n",
|
||||
" 3. Encoder-decoder attention (decoder queries attend to encoder outputs)\n",
|
||||
"- Counteracts reduced resolution from attention averaging through parallel processing\n",
|
||||
"\n",
|
||||
"Position-wise Feed-Forward Network:\n",
|
||||
"- Applied identically to each position separately\n",
|
||||
"- Consists of two linear transformations with ReLU activation\n",
|
||||
"- Structure: FFN(x) = max(0, xW1 + b1)W2 + b2\n",
|
||||
"- Input and output dimensionality: dmodel = 512\n",
|
||||
"- Inner-layer dimensionality: dff = 2048\n",
|
||||
"- Parameters vary between layers but remain constant across positions\n",
|
||||
"\n",
|
||||
"Position Encoding:\n",
|
||||
"- Adds positional information to input embeddings\n",
|
||||
"- Enables the model to consider sequential order without recurrence\n",
|
||||
"- Implements sinusoidal position encodings to allow model to attend to relative positions\n",
|
||||
"- Maintains constant number of operations between any two positions, unlike convolutional approaches\n",
|
||||
"- Allows effective modeling of both local and long-range dependencies\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Performance and Applications\n",
|
||||
"\n",
|
||||
"The Transformer model demonstrates significant performance advantages and practical applications across multiple domains:\n",
|
||||
"\n",
|
||||
"Performance Advantages over RNN/CNN Models:\n",
|
||||
"- Eliminates sequential computation constraints present in RNNs, enabling superior parallelization\n",
|
||||
"- Reduces operations needed for relating distant positions to a constant number, compared to linear/logarithmic scaling in CNNs\n",
|
||||
"- Processes all input and output positions simultaneously through self-attention mechanisms\n",
|
||||
"- Achieves state-of-the-art results while requiring significantly less computational resources\n",
|
||||
"\n",
|
||||
"Machine Translation Benchmarks:\n",
|
||||
"- WMT 2014 English-to-German: 28.4 BLEU score, exceeding previous best results by over 2 BLEU points\n",
|
||||
"- WMT 2014 English-to-French: 41.8 BLEU score (single-model state-of-the-art)\n",
|
||||
"- Surpasses performance of existing model ensembles in translation tasks\n",
|
||||
"\n",
|
||||
"Training Efficiency:\n",
|
||||
"- Requires only 3.5 days of training on eight GPUs for state-of-the-art performance\n",
|
||||
"- Achieves superior results at \"a small fraction of the training costs\" compared to previous models\n",
|
||||
"- Enables significantly faster training through parallel processing of input/output sequences\n",
|
||||
"- Can reach production-quality performance in as little as twelve hours on modern GPU hardware\n",
|
||||
"\n",
|
||||
"Real-world Applications:\n",
|
||||
"- Machine translation systems\n",
|
||||
"- Natural language understanding tasks\n",
|
||||
"- Reading comprehension\n",
|
||||
"- Abstractive summarization\n",
|
||||
"- Text entailment analysis\n",
|
||||
"- Constituency parsing (achieving 92.7 F1 score in semi-supervised settings)\n",
|
||||
"- Adaptable to both large and limited training data scenarios\n",
|
||||
"\n",
|
||||
"Scalability Benefits:\n",
|
||||
"- Highly parallelizable architecture enables efficient scaling across multiple GPUs\n",
|
||||
"- Constant computational complexity for relating any input/output positions\n",
|
||||
"- Effective handling of long-range dependencies in sequences\n",
|
||||
"- Maintains performance quality while scaling to larger datasets and model sizes\n",
|
||||
"- Generalizes well across different tasks and domains without architectural changes\n",
|
||||
"- Supports efficient inference and deployment in production environments\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"report_text = \"\\n\\n\".join([block.template for block in report.blocks])\n",
|
||||
"print(report_text)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Edit the final report\n",
|
||||
"\n",
|
||||
"Now that we have a report, we can edit it.\n",
|
||||
"\n",
|
||||
"We can use the `asuggest_edits` method to get suggestions for edits, and then use the `aaccept_edit`/`areject_edit` methods to apply them.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Justification for change: \n",
|
||||
"I'd suggest changing \"TLDR\" to \"Executive Summary\" which is more appropriate for a professional or academic report. This term is widely used in formal documents and better reflects the nature of this concise overview section while maintaining the same function of providing a quick summary of the key points.\n",
|
||||
"\n",
|
||||
"Proposed changes:\n",
|
||||
"## Executive Summary\n",
|
||||
"\n",
|
||||
"The Transformer introduced a revolutionary architecture that relies entirely on attention mechanisms, eliminating the need for recurrence or convolution in sequence processing. Its key innovations include multi-head self-attention for parallel processing of input sequences, scaled dot-product attention for efficient computation, and positional encodings for sequence order awareness. The model achieved breakthrough results in machine translation (28.4 BLEU on English-to-German, 41.8 BLEU on English-to-French) while requiring significantly less training time than previous approaches, training in 3.5 days on 8 GPUs. This architecture demonstrated that attention mechanisms alone are sufficient for state-of-the-art sequence modeling, setting a new direction for natural language processing.\n",
|
||||
"==================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"suggestions = await report_client.asuggest_edits(\n",
|
||||
" \"Can you change the TLDR header to something more professional?\"\n",
|
||||
")\n",
|
||||
"for suggestion in suggestions:\n",
|
||||
" print(\"Justification for change:\", suggestion.justification)\n",
|
||||
" print(\"Proposed changes:\")\n",
|
||||
" for block in suggestion.blocks:\n",
|
||||
" print(block.template)\n",
|
||||
" print(\"==================\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Changing to \"Executive Summary\" sounds reasonable, lets accept that!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for suggestion in suggestions:\n",
|
||||
" await report_client.aaccept_edit(suggestion)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 7. Print the final report\n",
|
||||
"\n",
|
||||
"Now that we have a report, we can print it."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"# Attention Is All You Need: A Pure Attention-Based Architecture for Neural Machine Translation\n",
|
||||
"\n",
|
||||
"## Executive Summary\n",
|
||||
"\n",
|
||||
"The Transformer introduced a revolutionary architecture that relies entirely on attention mechanisms, eliminating the need for recurrence or convolution in sequence processing. Its key innovations include multi-head self-attention for parallel processing of input sequences, scaled dot-product attention for efficient computation, and positional encodings for sequence order awareness. The model achieved breakthrough results in machine translation (28.4 BLEU on English-to-German, 41.8 BLEU on English-to-French) while requiring significantly less training time than previous approaches, training in 3.5 days on 8 GPUs. This architecture demonstrated that attention mechanisms alone are sufficient for state-of-the-art sequence modeling, setting a new direction for natural language processing.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Architecture Details\n",
|
||||
"\n",
|
||||
"The Transformer architecture represents a groundbreaking approach to sequence processing, built entirely on attention mechanisms without recurrence or convolution. Here are its key technical details:\n",
|
||||
"\n",
|
||||
"Core Components:\n",
|
||||
"- Encoder-decoder architecture with stacked self-attention and point-wise feed-forward layers\n",
|
||||
"- Each layer contains two main sub-layers: multi-head self-attention mechanism and position-wise feed-forward network\n",
|
||||
"- Layer normalization and residual connections between sub-layers\n",
|
||||
"- No recurrent or convolutional elements, enabling parallel processing\n",
|
||||
"\n",
|
||||
"Self-Attention Mechanism:\n",
|
||||
"- Processes relationships between all positions in a sequence simultaneously\n",
|
||||
"- Computes attention weights using queries, keys, and values derived from input representations\n",
|
||||
"- Implements scaled dot-product attention to prevent gradient issues with large input dimensions\n",
|
||||
"- Allows direct modeling of dependencies regardless of positional distance\n",
|
||||
"- Uses masking in decoder to prevent leftward information flow and maintain auto-regressive property\n",
|
||||
"\n",
|
||||
"Multi-Head Attention:\n",
|
||||
"- Employs multiple attention heads operating in parallel\n",
|
||||
"- Each head processes information in different representation subspaces\n",
|
||||
"- Three types of attention applications:\n",
|
||||
" 1. Encoder self-attention (all positions attend to each other)\n",
|
||||
" 2. Decoder self-attention (each position attends to previous positions)\n",
|
||||
" 3. Encoder-decoder attention (decoder queries attend to encoder outputs)\n",
|
||||
"- Counteracts reduced resolution from attention averaging through parallel processing\n",
|
||||
"\n",
|
||||
"Position-wise Feed-Forward Network:\n",
|
||||
"- Applied identically to each position separately\n",
|
||||
"- Consists of two linear transformations with ReLU activation\n",
|
||||
"- Structure: FFN(x) = max(0, xW1 + b1)W2 + b2\n",
|
||||
"- Input and output dimensionality: dmodel = 512\n",
|
||||
"- Inner-layer dimensionality: dff = 2048\n",
|
||||
"- Parameters vary between layers but remain constant across positions\n",
|
||||
"\n",
|
||||
"Position Encoding:\n",
|
||||
"- Adds positional information to input embeddings\n",
|
||||
"- Enables the model to consider sequential order without recurrence\n",
|
||||
"- Implements sinusoidal position encodings to allow model to attend to relative positions\n",
|
||||
"- Maintains constant number of operations between any two positions, unlike convolutional approaches\n",
|
||||
"- Allows effective modeling of both local and long-range dependencies\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Performance and Applications\n",
|
||||
"\n",
|
||||
"The Transformer model demonstrates significant performance advantages and practical applications across multiple domains:\n",
|
||||
"\n",
|
||||
"Performance Advantages over RNN/CNN Models:\n",
|
||||
"- Eliminates sequential computation constraints present in RNNs, enabling superior parallelization\n",
|
||||
"- Reduces operations needed for relating distant positions to a constant number, compared to linear/logarithmic scaling in CNNs\n",
|
||||
"- Processes all input and output positions simultaneously through self-attention mechanisms\n",
|
||||
"- Achieves state-of-the-art results while requiring significantly less computational resources\n",
|
||||
"\n",
|
||||
"Machine Translation Benchmarks:\n",
|
||||
"- WMT 2014 English-to-German: 28.4 BLEU score, exceeding previous best results by over 2 BLEU points\n",
|
||||
"- WMT 2014 English-to-French: 41.8 BLEU score (single-model state-of-the-art)\n",
|
||||
"- Surpasses performance of existing model ensembles in translation tasks\n",
|
||||
"\n",
|
||||
"Training Efficiency:\n",
|
||||
"- Requires only 3.5 days of training on eight GPUs for state-of-the-art performance\n",
|
||||
"- Achieves superior results at \"a small fraction of the training costs\" compared to previous models\n",
|
||||
"- Enables significantly faster training through parallel processing of input/output sequences\n",
|
||||
"- Can reach production-quality performance in as little as twelve hours on modern GPU hardware\n",
|
||||
"\n",
|
||||
"Real-world Applications:\n",
|
||||
"- Machine translation systems\n",
|
||||
"- Natural language understanding tasks\n",
|
||||
"- Reading comprehension\n",
|
||||
"- Abstractive summarization\n",
|
||||
"- Text entailment analysis\n",
|
||||
"- Constituency parsing (achieving 92.7 F1 score in semi-supervised settings)\n",
|
||||
"- Adaptable to both large and limited training data scenarios\n",
|
||||
"\n",
|
||||
"Scalability Benefits:\n",
|
||||
"- Highly parallelizable architecture enables efficient scaling across multiple GPUs\n",
|
||||
"- Constant computational complexity for relating any input/output positions\n",
|
||||
"- Effective handling of long-range dependencies in sequences\n",
|
||||
"- Maintains performance quality while scaling to larger datasets and model sizes\n",
|
||||
"- Generalizes well across different tasks and domains without architectural changes\n",
|
||||
"- Supports efficient inference and deployment in production environments\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"report_response = await report_client.aget()\n",
|
||||
"report_text = \"\\n\\n\".join([block.template for block in report_response.report.blocks])\n",
|
||||
"print(report_text)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can also see the sources for each block!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"0.99687636\n",
|
||||
"# Abstract\n",
|
||||
"\n",
|
||||
"The dominant sequence transduction models are based on complex recurrent or convolutiona\n",
|
||||
"==================\n",
|
||||
"0.99591404\n",
|
||||
"# 2 Background\n",
|
||||
"\n",
|
||||
"The goal of reducing sequential computation also forms the foundation of the Extende\n",
|
||||
"==================\n",
|
||||
"0.9951325\n",
|
||||
"# 1 Introduction\n",
|
||||
"\n",
|
||||
"Recurrent neural networks, long short-term memory [13] and gated recurrent [7] neu\n",
|
||||
"==================\n",
|
||||
"0.99442345\n",
|
||||
"# 7 Conclusion\n",
|
||||
"\n",
|
||||
"In this work, we presented the Transformer, the first sequence transduction model ba\n",
|
||||
"==================\n",
|
||||
"0.9967649\n",
|
||||
"# 3.2.3 Applications of Attention in our Model\n",
|
||||
"\n",
|
||||
"The Transformer uses multi-head attention in three d\n",
|
||||
"==================\n",
|
||||
"0.99533635\n",
|
||||
"# 2 Background\n",
|
||||
"\n",
|
||||
"The goal of reducing sequential computation also forms the foundation of the Extende\n",
|
||||
"==================\n",
|
||||
"0.9935868\n",
|
||||
"# Abstract\n",
|
||||
"\n",
|
||||
"The dominant sequence transduction models are based on complex recurrent or convolutiona\n",
|
||||
"==================\n",
|
||||
"0.98780584\n",
|
||||
"# Outputs\n",
|
||||
"\n",
|
||||
"(shifted right)\n",
|
||||
"\n",
|
||||
"Figure 1: The Transformer - model architecture.\n",
|
||||
"\n",
|
||||
"The Transformer follows\n",
|
||||
"==================\n",
|
||||
"0.9205043\n",
|
||||
"# 3.3 Position-wise Feed-Forward Networks\n",
|
||||
"\n",
|
||||
"In addition to attention sub-layers, each of the layers i\n",
|
||||
"==================\n",
|
||||
"0.79581684\n",
|
||||
"# 1 Introduction\n",
|
||||
"\n",
|
||||
"Recurrent neural networks, long short-term memory [13] and gated recurrent [7] neu\n",
|
||||
"==================\n",
|
||||
"0.9946774\n",
|
||||
"# Abstract\n",
|
||||
"\n",
|
||||
"The dominant sequence transduction models are based on complex recurrent or convolutiona\n",
|
||||
"==================\n",
|
||||
"0.97079873\n",
|
||||
"# 7 Conclusion\n",
|
||||
"\n",
|
||||
"In this work, we presented the Transformer, the first sequence transduction model ba\n",
|
||||
"==================\n",
|
||||
"0.9535353\n",
|
||||
"# 6.3 English Constituency Parsing\n",
|
||||
"\n",
|
||||
"To evaluate if the Transformer can generalize to other tasks we \n",
|
||||
"==================\n",
|
||||
"0.9514138\n",
|
||||
"# 2 Background\n",
|
||||
"\n",
|
||||
"The goal of reducing sequential computation also forms the foundation of the Extende\n",
|
||||
"==================\n",
|
||||
"0.9790758\n",
|
||||
"# 1 Introduction\n",
|
||||
"\n",
|
||||
"Recurrent neural networks, long short-term memory [13] and gated recurrent [7] neu\n",
|
||||
"==================\n",
|
||||
"0.92262185\n",
|
||||
"# Outputs\n",
|
||||
"\n",
|
||||
"(shifted right)\n",
|
||||
"\n",
|
||||
"Figure 1: The Transformer - model architecture.\n",
|
||||
"\n",
|
||||
"The Transformer follows\n",
|
||||
"==================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for block in report_response.report.blocks:\n",
|
||||
" # Each block has a list of sources, which are the nodes that were used to generate the block\n",
|
||||
" for source in block.sources:\n",
|
||||
" print(source.score)\n",
|
||||
" print(source.node.text[:100])\n",
|
||||
" print(\"==================\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "llama-parse-aNC435Vv-py3.10",
|
||||
"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": 2
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "llama-cloud-services-workspace",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"devDependencies": {
|
||||
"prettier": "^3.6.2",
|
||||
"lint-staged": "^15.4.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"ts/llama_cloud_services/src/**/*.{ts,tsx,js,jsx}": [
|
||||
"pnpm --filter llama-cloud-services exec eslint --fix",
|
||||
"pnpm --filter llama-cloud-services exec prettier --write"
|
||||
]
|
||||
},
|
||||
"packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912"
|
||||
}
|
||||
Generated
+245
@@ -6,6 +6,15 @@ settings:
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
lint-staged:
|
||||
specifier: ^15.4.2
|
||||
version: 15.5.2
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2
|
||||
|
||||
ts/e2e-tests:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
@@ -829,6 +838,10 @@ packages:
|
||||
ajv@8.17.1:
|
||||
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||
|
||||
ansi-escapes@7.0.0:
|
||||
resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -933,6 +946,10 @@ packages:
|
||||
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
cli-truncate@4.0.0:
|
||||
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
cliui@8.0.1:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -944,10 +961,17 @@ packages:
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
|
||||
commander@13.0.0:
|
||||
resolution: {integrity: sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commander@13.1.0:
|
||||
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commondir@1.0.1:
|
||||
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||
|
||||
@@ -1007,6 +1031,10 @@ packages:
|
||||
emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
environment@1.1.0:
|
||||
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
es-module-lexer@1.7.0:
|
||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||
|
||||
@@ -1071,6 +1099,13 @@ packages:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
execa@8.0.1:
|
||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
|
||||
expect-type@1.2.2:
|
||||
resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -1152,6 +1187,14 @@ packages:
|
||||
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
get-east-asian-width@1.3.1:
|
||||
resolution: {integrity: sha512-R1QfovbPsKmosqTnPoRFiJ7CF9MLRgb53ChvMZm+r4p76/+8yKDy17qLL2PKInORy2RkZZekuK0efYgmzTkXyQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
get-stream@8.0.1:
|
||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
get-tsconfig@4.10.1:
|
||||
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
||||
|
||||
@@ -1198,6 +1241,10 @@ packages:
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
human-signals@5.0.0:
|
||||
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
@@ -1229,6 +1276,14 @@ packages:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-fullwidth-code-point@4.0.0:
|
||||
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
is-fullwidth-code-point@5.1.0:
|
||||
resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1251,6 +1306,10 @@ packages:
|
||||
is-reference@1.2.1:
|
||||
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
||||
|
||||
is-stream@3.0.0:
|
||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
is-unicode-supported@1.3.0:
|
||||
resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1314,6 +1373,19 @@ packages:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
lilconfig@3.1.3:
|
||||
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
lint-staged@15.5.2:
|
||||
resolution: {integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
hasBin: true
|
||||
|
||||
listr2@8.3.3:
|
||||
resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
locate-path@6.0.0:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1328,6 +1400,10 @@ packages:
|
||||
resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
log-update@6.1.0:
|
||||
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
loupe@3.2.0:
|
||||
resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==}
|
||||
|
||||
@@ -1347,6 +1423,9 @@ packages:
|
||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
merge-stream@2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
|
||||
merge2@1.4.1:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -1355,6 +1434,10 @@ packages:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
mimic-fn@4.0.0:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
mimic-function@5.0.1:
|
||||
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1414,6 +1497,10 @@ packages:
|
||||
node-fetch-native@1.6.7:
|
||||
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
|
||||
|
||||
npm-run-path@5.3.0:
|
||||
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
nypm@0.5.4:
|
||||
resolution: {integrity: sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==}
|
||||
engines: {node: ^14.16.0 || >=16.10.0}
|
||||
@@ -1422,6 +1509,10 @@ packages:
|
||||
ohash@1.1.6:
|
||||
resolution: {integrity: sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==}
|
||||
|
||||
onetime@6.0.0:
|
||||
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
onetime@7.0.0:
|
||||
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1461,6 +1552,10 @@ packages:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-key@4.0.0:
|
||||
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
@@ -1492,6 +1587,11 @@ packages:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pidtree@0.6.0:
|
||||
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
|
||||
pkg-types@1.3.1:
|
||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||
|
||||
@@ -1558,6 +1658,9 @@ packages:
|
||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
rollup-plugin-dts@6.2.1:
|
||||
resolution: {integrity: sha512-sR3CxYUl7i2CHa0O7bA45mCrgADyAQ0tVtGSqi3yvH28M+eg1+g5d7kQ9hLvEz5dorK3XVsH5L2jwHLQf72DzA==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -1609,6 +1712,14 @@ packages:
|
||||
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
slice-ansi@5.0.0:
|
||||
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
slice-ansi@7.1.0:
|
||||
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1627,6 +1738,10 @@ packages:
|
||||
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
string-argv@0.3.2:
|
||||
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
|
||||
engines: {node: '>=0.6.19'}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1647,6 +1762,10 @@ packages:
|
||||
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-final-newline@3.0.0:
|
||||
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-json-comments@3.1.1:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1871,6 +1990,10 @@ packages:
|
||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
wrap-ansi@9.0.0:
|
||||
resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -1878,6 +2001,11 @@ packages:
|
||||
yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
|
||||
yaml@2.8.1:
|
||||
resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -2551,6 +2679,10 @@ snapshots:
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
|
||||
ansi-escapes@7.0.0:
|
||||
dependencies:
|
||||
environment: 1.1.0
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-regex@6.1.0: {}
|
||||
@@ -2665,6 +2797,11 @@ snapshots:
|
||||
|
||||
cli-spinners@2.9.2: {}
|
||||
|
||||
cli-truncate@4.0.0:
|
||||
dependencies:
|
||||
slice-ansi: 5.0.0
|
||||
string-width: 7.2.0
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
@@ -2677,8 +2814,12 @@ snapshots:
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
colorette@2.0.20: {}
|
||||
|
||||
commander@13.0.0: {}
|
||||
|
||||
commander@13.1.0: {}
|
||||
|
||||
commondir@1.0.1: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
@@ -2717,6 +2858,8 @@ snapshots:
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
|
||||
environment@1.1.0: {}
|
||||
|
||||
es-module-lexer@1.7.0: {}
|
||||
|
||||
esbuild@0.21.5:
|
||||
@@ -2824,6 +2967,20 @@ snapshots:
|
||||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
eventemitter3@5.0.1: {}
|
||||
|
||||
execa@8.0.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
get-stream: 8.0.1
|
||||
human-signals: 5.0.0
|
||||
is-stream: 3.0.0
|
||||
merge-stream: 2.0.0
|
||||
npm-run-path: 5.3.0
|
||||
onetime: 6.0.0
|
||||
signal-exit: 4.1.0
|
||||
strip-final-newline: 3.0.0
|
||||
|
||||
expect-type@1.2.2: {}
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
@@ -2899,6 +3056,10 @@ snapshots:
|
||||
|
||||
get-east-asian-width@1.3.0: {}
|
||||
|
||||
get-east-asian-width@1.3.1: {}
|
||||
|
||||
get-stream@8.0.1: {}
|
||||
|
||||
get-tsconfig@4.10.1:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
@@ -2953,6 +3114,8 @@ snapshots:
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
human-signals@5.0.0: {}
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
@@ -2974,6 +3137,12 @@ snapshots:
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
is-fullwidth-code-point@4.0.0: {}
|
||||
|
||||
is-fullwidth-code-point@5.1.0:
|
||||
dependencies:
|
||||
get-east-asian-width: 1.3.1
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
@@ -2990,6 +3159,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
||||
is-stream@3.0.0: {}
|
||||
|
||||
is-unicode-supported@1.3.0: {}
|
||||
|
||||
is-unicode-supported@2.1.0: {}
|
||||
@@ -3053,6 +3224,32 @@ snapshots:
|
||||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
lilconfig@3.1.3: {}
|
||||
|
||||
lint-staged@15.5.2:
|
||||
dependencies:
|
||||
chalk: 5.5.0
|
||||
commander: 13.1.0
|
||||
debug: 4.4.1
|
||||
execa: 8.0.1
|
||||
lilconfig: 3.1.3
|
||||
listr2: 8.3.3
|
||||
micromatch: 4.0.8
|
||||
pidtree: 0.6.0
|
||||
string-argv: 0.3.2
|
||||
yaml: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
listr2@8.3.3:
|
||||
dependencies:
|
||||
cli-truncate: 4.0.0
|
||||
colorette: 2.0.20
|
||||
eventemitter3: 5.0.1
|
||||
log-update: 6.1.0
|
||||
rfdc: 1.4.1
|
||||
wrap-ansi: 9.0.0
|
||||
|
||||
locate-path@6.0.0:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
@@ -3066,6 +3263,14 @@ snapshots:
|
||||
chalk: 5.5.0
|
||||
is-unicode-supported: 1.3.0
|
||||
|
||||
log-update@6.1.0:
|
||||
dependencies:
|
||||
ansi-escapes: 7.0.0
|
||||
cli-cursor: 5.0.0
|
||||
slice-ansi: 7.1.0
|
||||
strip-ansi: 7.1.0
|
||||
wrap-ansi: 9.0.0
|
||||
|
||||
loupe@3.2.0: {}
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
@@ -3086,6 +3291,8 @@ snapshots:
|
||||
dependencies:
|
||||
semver: 7.7.2
|
||||
|
||||
merge-stream@2.0.0: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
micromatch@4.0.8:
|
||||
@@ -3093,6 +3300,8 @@ snapshots:
|
||||
braces: 3.0.3
|
||||
picomatch: 2.3.1
|
||||
|
||||
mimic-fn@4.0.0: {}
|
||||
|
||||
mimic-function@5.0.1: {}
|
||||
|
||||
minimatch@3.1.2:
|
||||
@@ -3139,6 +3348,10 @@ snapshots:
|
||||
|
||||
node-fetch-native@1.6.7: {}
|
||||
|
||||
npm-run-path@5.3.0:
|
||||
dependencies:
|
||||
path-key: 4.0.0
|
||||
|
||||
nypm@0.5.4:
|
||||
dependencies:
|
||||
citty: 0.1.6
|
||||
@@ -3150,6 +3363,10 @@ snapshots:
|
||||
|
||||
ohash@1.1.6: {}
|
||||
|
||||
onetime@6.0.0:
|
||||
dependencies:
|
||||
mimic-fn: 4.0.0
|
||||
|
||||
onetime@7.0.0:
|
||||
dependencies:
|
||||
mimic-function: 5.0.1
|
||||
@@ -3199,6 +3416,8 @@ snapshots:
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-key@4.0.0: {}
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
@@ -3220,6 +3439,8 @@ snapshots:
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pidtree@0.6.0: {}
|
||||
|
||||
pkg-types@1.3.1:
|
||||
dependencies:
|
||||
confbox: 0.1.8
|
||||
@@ -3272,6 +3493,8 @@ snapshots:
|
||||
|
||||
reusify@1.1.0: {}
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rollup-plugin-dts@6.2.1(rollup@4.46.2)(typescript@5.9.2):
|
||||
dependencies:
|
||||
magic-string: 0.30.17
|
||||
@@ -3342,6 +3565,16 @@ snapshots:
|
||||
mrmime: 2.0.1
|
||||
totalist: 3.0.1
|
||||
|
||||
slice-ansi@5.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
is-fullwidth-code-point: 4.0.0
|
||||
|
||||
slice-ansi@7.1.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
is-fullwidth-code-point: 5.1.0
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
source-map@0.6.1: {}
|
||||
@@ -3352,6 +3585,8 @@ snapshots:
|
||||
|
||||
stdin-discarder@0.2.2: {}
|
||||
|
||||
string-argv@0.3.2: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
@@ -3378,6 +3613,8 @@ snapshots:
|
||||
dependencies:
|
||||
ansi-regex: 6.1.0
|
||||
|
||||
strip-final-newline@3.0.0: {}
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
strtok3@10.3.4:
|
||||
@@ -3584,10 +3821,18 @@ snapshots:
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
wrap-ansi@9.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
string-width: 7.2.0
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yallist@4.0.0: {}
|
||||
|
||||
yaml@2.8.1: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yargs@17.7.2:
|
||||
|
||||
+1
-7
@@ -9,7 +9,6 @@ This repository contains the code for hand-written SDKs and clients for interact
|
||||
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.
|
||||
|
||||
@@ -28,14 +27,12 @@ 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
|
||||
from llama_cloud_services import LlamaParse, 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"
|
||||
@@ -45,7 +42,6 @@ index = LlamaCloudIndex(
|
||||
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)
|
||||
|
||||
@@ -58,13 +54,11 @@ You can also create your API key in the EU region [here](https://cloud.eu.llamai
|
||||
```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",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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.extract import LlamaExtract, ExtractionAgent, SourceText
|
||||
from llama_cloud_services.constants import EU_BASE_URL
|
||||
from llama_cloud_services.index import (
|
||||
LlamaCloudCompositeRetriever,
|
||||
@@ -10,10 +9,9 @@ from llama_cloud_services.index import (
|
||||
|
||||
__all__ = [
|
||||
"LlamaParse",
|
||||
"ReportClient",
|
||||
"LlamaReport",
|
||||
"LlamaExtract",
|
||||
"ExtractionAgent",
|
||||
"SourceText",
|
||||
"EU_BASE_URL",
|
||||
"LlamaCloudIndex",
|
||||
"LlamaCloudRetriever",
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import os
|
||||
from typing import Any, Dict, Generic, List, Optional, Type
|
||||
|
||||
from llama_cloud import (
|
||||
AgentData,
|
||||
PaginatedResponseAgentData,
|
||||
PaginatedResponseAggregateGroup,
|
||||
)
|
||||
from llama_cloud.client import AsyncLlamaCloud
|
||||
from tenacity import (
|
||||
WrappedFn,
|
||||
@@ -86,7 +91,7 @@ class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
client=llama_client,
|
||||
type=ExtractedPerson,
|
||||
collection="extracted_people",
|
||||
agent_url_id="person-extraction-agent"
|
||||
deployment_name="person-extraction-agent"
|
||||
)
|
||||
|
||||
# Create data
|
||||
@@ -109,10 +114,12 @@ class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
self,
|
||||
type: Type[AgentDataT],
|
||||
collection: str = "default",
|
||||
agent_url_id: Optional[str] = None,
|
||||
deployment_name: Optional[str] = None,
|
||||
client: Optional[AsyncLlamaCloud] = None,
|
||||
token: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
# deprecated, use deployment_name instead
|
||||
agent_url_id: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Initialize the AsyncAgentDataClient.
|
||||
@@ -123,11 +130,11 @@ class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
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.
|
||||
deployment_name: Unique identifier for the agent deployment. 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
|
||||
@@ -135,15 +142,14 @@ class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
defaults to https://api.cloud.llamaindex.ai
|
||||
|
||||
Raises:
|
||||
ValueError: If agent_url_id is not provided and the
|
||||
ValueError: If deployment_name 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.deployment_name = deployment_name or agent_url_id or get_default_agent_id()
|
||||
|
||||
self.collection = collection
|
||||
if not client:
|
||||
@@ -156,15 +162,19 @@ class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
|
||||
@agent_data_retry
|
||||
async def get_item(self, item_id: str) -> TypedAgentData[AgentDataT]:
|
||||
raw_data = await self.client.beta.get_agent_data(
|
||||
raw_data = await self.untyped_get_item(item_id)
|
||||
return TypedAgentData.from_raw(raw_data, self.type)
|
||||
|
||||
@agent_data_retry
|
||||
async def untyped_get_item(self, item_id: str) -> AgentData:
|
||||
return 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,
|
||||
deployment_name=self.deployment_name,
|
||||
collection=self.collection,
|
||||
data=data.model_dump(),
|
||||
)
|
||||
@@ -210,9 +220,7 @@ class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
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,
|
||||
raw = await self.untyped_search(
|
||||
filter=filter,
|
||||
order_by=order_by,
|
||||
offset=offset,
|
||||
@@ -227,6 +235,25 @@ class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
total=raw.total_size,
|
||||
)
|
||||
|
||||
@agent_data_retry
|
||||
async def untyped_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,
|
||||
) -> PaginatedResponseAgentData:
|
||||
return await self.client.beta.search_agent_data_api_v_1_beta_agent_data_search_post(
|
||||
deployment_name=self.deployment_name,
|
||||
collection=self.collection,
|
||||
filter=filter,
|
||||
order_by=order_by,
|
||||
offset=offset,
|
||||
page_size=page_size,
|
||||
include_total=include_total,
|
||||
)
|
||||
|
||||
@agent_data_retry
|
||||
async def aggregate(
|
||||
self,
|
||||
@@ -253,8 +280,38 @@ class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
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,
|
||||
raw = await self.untyped_aggregate(
|
||||
filter=filter,
|
||||
group_by=group_by,
|
||||
count=count,
|
||||
first=first,
|
||||
order_by=order_by,
|
||||
offset=offset,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
return TypedAggregateGroupItems(
|
||||
items=[
|
||||
TypedAggregateGroup.from_raw(grp, validator=self.type)
|
||||
for grp in raw.items
|
||||
],
|
||||
has_more=raw.next_page_token is not None,
|
||||
total=raw.total_size,
|
||||
)
|
||||
|
||||
@agent_data_retry
|
||||
async def untyped_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,
|
||||
) -> PaginatedResponseAggregateGroup:
|
||||
return await self.client.beta.aggregate_agent_data_api_v_1_beta_agent_data_aggregate_post(
|
||||
deployment_name=self.deployment_name,
|
||||
collection=self.collection,
|
||||
page_size=page_size,
|
||||
filter=filter,
|
||||
@@ -264,11 +321,3 @@ class AsyncAgentDataClient(Generic[AgentDataT]):
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ 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
|
||||
- Deployment Name: Unique identifier for an agent deployment
|
||||
- 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
|
||||
|
||||
@@ -26,7 +26,7 @@ Example Usage:
|
||||
client=async_llama_cloud,
|
||||
type=Person,
|
||||
collection="people",
|
||||
agent_url_id="my-extraction-agent-xyz"
|
||||
deployment_name="my-extraction-agent-xyz"
|
||||
)
|
||||
|
||||
# Create typed data
|
||||
@@ -56,7 +56,6 @@ from typing import (
|
||||
|
||||
# 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])
|
||||
|
||||
@@ -78,7 +77,7 @@ class TypedAgentData(BaseModel, Generic[AgentDataT]):
|
||||
|
||||
Attributes:
|
||||
id: Unique identifier for this data record
|
||||
agent_url_id: Identifier of the agent that created this data
|
||||
deployment_name: Identifier of the agent deployment 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
|
||||
@@ -94,8 +93,8 @@ class TypedAgentData(BaseModel, Generic[AgentDataT]):
|
||||
"""
|
||||
|
||||
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"
|
||||
deployment_name: str = Field(
|
||||
description="Identifier of the agent deployment that created this data"
|
||||
)
|
||||
collection: Optional[str] = Field(
|
||||
description="Named collection within the agent for data organization"
|
||||
@@ -116,15 +115,15 @@ class TypedAgentData(BaseModel, Generic[AgentDataT]):
|
||||
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,
|
||||
deployment_name=raw_data.deployment_name,
|
||||
collection=raw_data.collection,
|
||||
data=data,
|
||||
created_at=raw_data.created_at,
|
||||
|
||||
@@ -9,7 +9,6 @@ from llama_cloud.types import (
|
||||
ClassifyJobResults,
|
||||
ClassifyParsingConfiguration,
|
||||
StatusEnum,
|
||||
ClassifyJobWithStatus,
|
||||
File,
|
||||
)
|
||||
from llama_cloud.resources.classifier.client import OMIT
|
||||
@@ -17,6 +16,9 @@ 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
|
||||
from llama_cloud_services.beta.classifier.types import (
|
||||
ClassifyJobResultsWithFiles,
|
||||
)
|
||||
|
||||
|
||||
class ClassificationOutput(BaseModel):
|
||||
@@ -52,6 +54,24 @@ class ClassifyClient:
|
||||
self.file_client = FileClient(client, project_id, organization_id)
|
||||
self.polling_timeout = polling_timeout
|
||||
|
||||
@classmethod
|
||||
def from_api_key(
|
||||
cls,
|
||||
api_key: str,
|
||||
project_id: Optional[str] = None,
|
||||
organization_id: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
) -> "ClassifyClient":
|
||||
"""
|
||||
Create a classify client from an API key.
|
||||
"""
|
||||
client = AsyncLlamaCloud(token=api_key, base_url=base_url)
|
||||
return cls(
|
||||
client,
|
||||
project_id,
|
||||
organization_id,
|
||||
)
|
||||
|
||||
async def acreate_classify_job(
|
||||
self,
|
||||
rules: list[ClassifierRule],
|
||||
@@ -152,11 +172,12 @@ class ClassifyClient:
|
||||
file_input_path: str,
|
||||
parsing_configuration: Optional[ClassifyParsingConfiguration] = None,
|
||||
raise_on_error: bool = True,
|
||||
) -> ClassifyJobResults:
|
||||
) -> ClassifyJobResultsWithFiles:
|
||||
file = await self.file_client.upload_file(file_input_path)
|
||||
return await self.aclassify_file_ids(
|
||||
results = await self.aclassify_file_ids(
|
||||
rules, [file.id], parsing_configuration, raise_on_error
|
||||
)
|
||||
return ClassifyJobResultsWithFiles.from_classify_job_results(results, [file])
|
||||
|
||||
def classify_file_path(
|
||||
self,
|
||||
@@ -164,7 +185,7 @@ class ClassifyClient:
|
||||
file_input_path: str,
|
||||
parsing_configuration: Optional[ClassifyParsingConfiguration] = None,
|
||||
raise_on_error: bool = True,
|
||||
) -> ClassifyJobResults:
|
||||
) -> ClassifyJobResultsWithFiles:
|
||||
with augment_async_errors():
|
||||
return asyncio.run(
|
||||
self.aclassify_file_path(
|
||||
@@ -180,7 +201,7 @@ class ClassifyClient:
|
||||
raise_on_error: bool = True,
|
||||
workers: int = DEFAULT_NUM_WORKERS,
|
||||
show_progress: bool = False,
|
||||
) -> ClassifyJobResults:
|
||||
) -> ClassifyJobResultsWithFiles:
|
||||
coroutines = [self.file_client.upload_file(path) for path in file_input_paths]
|
||||
files: list[File] = await run_jobs(
|
||||
coroutines,
|
||||
@@ -188,9 +209,10 @@ class ClassifyClient:
|
||||
workers=workers,
|
||||
desc="Uploading files for classification",
|
||||
)
|
||||
return await self.aclassify_file_ids(
|
||||
results = await self.aclassify_file_ids(
|
||||
rules, [file.id for file in files], parsing_configuration, raise_on_error
|
||||
)
|
||||
return ClassifyJobResultsWithFiles.from_classify_job_results(results, files)
|
||||
|
||||
def classify_file_paths(
|
||||
self,
|
||||
@@ -198,7 +220,7 @@ class ClassifyClient:
|
||||
file_input_paths: list[str],
|
||||
parsing_configuration: Optional[ClassifyParsingConfiguration] = None,
|
||||
raise_on_error: bool = True,
|
||||
) -> ClassifyJobResults:
|
||||
) -> ClassifyJobResultsWithFiles:
|
||||
with augment_async_errors():
|
||||
return asyncio.run(
|
||||
self.aclassify_file_paths(
|
||||
@@ -206,7 +228,7 @@ class ClassifyClient:
|
||||
)
|
||||
)
|
||||
|
||||
async def wait_for_job_completion(self, job_id: str) -> ClassifyJobWithStatus:
|
||||
async def wait_for_job_completion(self, job_id: str) -> ClassifyJob:
|
||||
"""
|
||||
Wait for a classify job to complete.
|
||||
Meant to expose lower level access to classifier jobs for advanced use cases.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
from llama_cloud.types.classify_job_results import ClassifyJobResults
|
||||
from llama_cloud.types.file_classification import FileClassification
|
||||
from llama_cloud.types.file import File
|
||||
|
||||
|
||||
class FileClassificationWithFile(FileClassification):
|
||||
"""
|
||||
File classification with file object.
|
||||
"""
|
||||
|
||||
file: File
|
||||
|
||||
@classmethod
|
||||
def from_file_classification(
|
||||
cls, file_classification: FileClassification, file: File
|
||||
) -> "FileClassificationWithFile":
|
||||
if file_classification.file_id != file.id:
|
||||
raise ValueError(
|
||||
f"File classification ID {file_classification.id} does not match file ID {file.id}"
|
||||
)
|
||||
ctor_args = {
|
||||
**file_classification.dict(),
|
||||
"file": file,
|
||||
}
|
||||
return cls(**ctor_args)
|
||||
|
||||
|
||||
class ClassifyJobResultsWithFiles(ClassifyJobResults):
|
||||
"""
|
||||
Classify job results with file objects.
|
||||
"""
|
||||
|
||||
items: list[FileClassificationWithFile]
|
||||
|
||||
@classmethod
|
||||
def from_classify_job_results(
|
||||
cls, classify_job_results: ClassifyJobResults, files: list[File]
|
||||
) -> "ClassifyJobResultsWithFiles":
|
||||
if len(classify_job_results.items) != len(files):
|
||||
raise ValueError(
|
||||
f"Number of classify job results {len(classify_job_results.items)} does not match number of files {len(files)}"
|
||||
)
|
||||
# create mapping of file classification result to file object
|
||||
file_id_to_file: dict[str, File] = {file.id: file for file in files}
|
||||
file_classification_to_file: list[tuple[FileClassification, File]] = []
|
||||
for item in classify_job_results.items:
|
||||
if item.file_id not in file_id_to_file:
|
||||
raise ValueError(
|
||||
f"File classification result {item.id} has file ID {item.file_id} that does not match any provided file ID"
|
||||
)
|
||||
file_classification_to_file.append((item, file_id_to_file[item.file_id]))
|
||||
|
||||
# create a list of file classification with file objects
|
||||
ctor_args = classify_job_results.dict()
|
||||
ctor_args["items"] = [
|
||||
FileClassificationWithFile.from_file_classification(item, file)
|
||||
for item, file in file_classification_to_file
|
||||
]
|
||||
return cls(**ctor_args)
|
||||
@@ -489,6 +489,7 @@ class LlamaCloudIndex(BaseManagedIndex):
|
||||
name: str,
|
||||
project_name: str = DEFAULT_PROJECT_NAME,
|
||||
organization_id: Optional[str] = None,
|
||||
project_id: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
app_url: Optional[str] = None,
|
||||
@@ -504,15 +505,15 @@ class LlamaCloudIndex(BaseManagedIndex):
|
||||
app_url = app_url or os.environ.get("LLAMA_CLOUD_APP_URL", DEFAULT_APP_URL)
|
||||
client = get_client(api_key, base_url, app_url, timeout)
|
||||
|
||||
# create project if it doesn't exist
|
||||
project = client.projects.upsert_project(
|
||||
organization_id=organization_id, request=ProjectCreate(name=project_name)
|
||||
)
|
||||
if project.id is None:
|
||||
raise ValueError(f"Failed to create/get project {project_name}")
|
||||
|
||||
if verbose:
|
||||
print(f"Created project {project.id} with name {project.name}")
|
||||
if project_id is None:
|
||||
# create project if it doesn't exist
|
||||
project = client.projects.upsert_project(
|
||||
organization_id=organization_id,
|
||||
request=ProjectCreate(name=project_name),
|
||||
)
|
||||
project_id = project.id
|
||||
if verbose:
|
||||
print(f"Created project {project_id} with name {project_name}")
|
||||
|
||||
# create pipeline
|
||||
pipeline_create = PipelineCreate(
|
||||
@@ -523,7 +524,7 @@ class LlamaCloudIndex(BaseManagedIndex):
|
||||
llama_parse_parameters=llama_parse_parameters or LlamaParseParameters(),
|
||||
)
|
||||
pipeline = client.pipelines.upsert_pipeline(
|
||||
project_id=project.id, request=pipeline_create
|
||||
project_id=project_id, request=pipeline_create
|
||||
)
|
||||
if pipeline.id is None:
|
||||
raise ValueError(f"Failed to create/get pipeline {name}")
|
||||
@@ -532,8 +533,7 @@ class LlamaCloudIndex(BaseManagedIndex):
|
||||
|
||||
return cls(
|
||||
name,
|
||||
project_name=project.name,
|
||||
organization_id=project.organization_id,
|
||||
project_id=project_id,
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
app_url=app_url,
|
||||
@@ -606,6 +606,7 @@ class LlamaCloudIndex(BaseManagedIndex):
|
||||
name: str,
|
||||
project_name: str = DEFAULT_PROJECT_NAME,
|
||||
organization_id: Optional[str] = None,
|
||||
project_id: Optional[str] = None,
|
||||
api_key: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
app_url: Optional[str] = None,
|
||||
@@ -631,6 +632,7 @@ class LlamaCloudIndex(BaseManagedIndex):
|
||||
verbose=verbose,
|
||||
embedding_config=embedding_config,
|
||||
transform_config=transform_config,
|
||||
project_id=project_id,
|
||||
)
|
||||
|
||||
app_url = app_url or os.environ.get("LLAMA_CLOUD_APP_URL", DEFAULT_APP_URL)
|
||||
|
||||
@@ -396,6 +396,10 @@ class LlamaParse(BasePydanticReader):
|
||||
default=False,
|
||||
description="If set, the parser will try to preserve very small text lines. This can be useful for documents containing vector graphics with very small text lines that may not be recognized by OCR or a vision model (such as in CAD drawings).",
|
||||
)
|
||||
precise_bounding_box: Optional[bool] = Field(
|
||||
default=False,
|
||||
description="If set to true, the parser will use a more precise bounding box to extract text from documents. This will increase the accuracy of the parsing job, but reduce the speed.",
|
||||
)
|
||||
replace_failed_page_mode: Optional[FailedPageMode] = Field(
|
||||
default=None,
|
||||
description="The mode to use to replace the failed page, see FailedPageMode enum for possible value. If set, the parser will replace the failed page with the specified mode. If not set, the default mode (raw_text) will be used.",
|
||||
@@ -416,7 +420,22 @@ class LlamaParse(BasePydanticReader):
|
||||
default=False,
|
||||
description="If set to true, the parser will extract sub-tables from the spreadsheet when possible (more than one table per sheet).",
|
||||
)
|
||||
|
||||
spreadsheet_force_formula_computation: Optional[bool] = Field(
|
||||
default=False,
|
||||
description="If set to true, the parser will re-compute values for all spreadsheet cells containing formulas.",
|
||||
)
|
||||
specialized_chart_parsing_agentic: Optional[bool] = Field(
|
||||
default=False,
|
||||
description="If set to true, the parser will use a specialized agentic chart parsing model to extract data from charts. This model is able to understand the chart type and extract the data accordingly.",
|
||||
)
|
||||
specialized_chart_parsing_efficient: Optional[bool] = Field(
|
||||
default=False,
|
||||
description="If set to true, the parser will use a specialized efficient chart parsing model to extract data from charts. This model is faster and cheaper than the agentic model, but may be less accurate.",
|
||||
)
|
||||
specialized_chart_parsing_plus: Optional[bool] = Field(
|
||||
default=False,
|
||||
description="If set to true, the parser will use a specialized one-shot chart parsing model to extract data from charts. This model is able to understand the chart type and extract the data accordingly. It is more accurate than the efficient model, but also more expensive.",
|
||||
)
|
||||
strict_mode_buggy_font: Optional[bool] = Field(
|
||||
default=False,
|
||||
description="If set to true, the parser will fail if it can't extract text from a document because of a buggy font.",
|
||||
@@ -928,6 +947,9 @@ class LlamaParse(BasePydanticReader):
|
||||
if self.preset is not None:
|
||||
data["preset"] = self.preset
|
||||
|
||||
if self.precise_bounding_box:
|
||||
data["precise_bounding_box"] = self.precise_bounding_box
|
||||
|
||||
if self.replace_failed_page_mode is not None:
|
||||
data["replace_failed_page_mode"] = self.replace_failed_page_mode.value
|
||||
|
||||
@@ -947,6 +969,24 @@ class LlamaParse(BasePydanticReader):
|
||||
if self.spreadsheet_extract_sub_tables:
|
||||
data["spreadsheet_extract_sub_tables"] = self.spreadsheet_extract_sub_tables
|
||||
|
||||
if self.spreadsheet_force_formula_computation:
|
||||
data[
|
||||
"spreadsheet_force_formula_computation"
|
||||
] = self.spreadsheet_force_formula_computation
|
||||
|
||||
if self.specialized_chart_parsing_agentic:
|
||||
data[
|
||||
"specialized_chart_parsing_agentic"
|
||||
] = self.specialized_chart_parsing_agentic
|
||||
|
||||
if self.specialized_chart_parsing_efficient:
|
||||
data[
|
||||
"specialized_chart_parsing_efficient"
|
||||
] = self.specialized_chart_parsing_efficient
|
||||
|
||||
if self.specialized_chart_parsing_plus:
|
||||
data["specialized_chart_parsing_plus"] = self.specialized_chart_parsing_plus
|
||||
|
||||
if self.strict_mode_buggy_font:
|
||||
data["strict_mode_buggy_font"] = self.strict_mode_buggy_font
|
||||
|
||||
|
||||
@@ -159,6 +159,9 @@ class Page(BaseModel):
|
||||
durationInSeconds: Optional[float] = Field(
|
||||
default=None, description="The duration of the audio transcript in seconds."
|
||||
)
|
||||
slideSpeakerNotes: Optional[str] = Field(
|
||||
default=None, description="The speaker notes for the slide."
|
||||
)
|
||||
|
||||
|
||||
class JobResult(BaseModel):
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from llama_cloud_services.report.report import ReportClient
|
||||
from llama_cloud_services.report.base import LlamaReport
|
||||
|
||||
__all__ = ["ReportClient", "LlamaReport"]
|
||||
@@ -1,269 +0,0 @@
|
||||
import asyncio
|
||||
import httpx
|
||||
import os
|
||||
import io
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Optional, List, Union, Any, Coroutine, TypeVar
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from llama_cloud.types import ReportMetadata
|
||||
from llama_cloud_services.report.report import ReportClient
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class LlamaReport:
|
||||
"""Client for managing reports and general report operations."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
project_id: Optional[str] = None,
|
||||
organization_id: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
timeout: Optional[int] = None,
|
||||
async_httpx_client: Optional[httpx.AsyncClient] = None,
|
||||
):
|
||||
self.api_key = api_key or os.getenv("LLAMA_CLOUD_API_KEY", None)
|
||||
if not self.api_key:
|
||||
raise ValueError("No API key provided.")
|
||||
|
||||
self.base_url = base_url or os.getenv(
|
||||
"LLAMA_CLOUD_BASE_URL", "https://api.cloud.llamaindex.ai"
|
||||
)
|
||||
self.timeout = timeout or 60
|
||||
|
||||
# Initialize HTTP clients
|
||||
self._aclient = async_httpx_client or httpx.AsyncClient(timeout=self.timeout)
|
||||
|
||||
# Set auth headers
|
||||
self.headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
}
|
||||
|
||||
self.organization_id = organization_id
|
||||
self.project_id = project_id
|
||||
self._client_params = {
|
||||
"timeout": self._aclient.timeout,
|
||||
"headers": self._aclient.headers,
|
||||
"base_url": self._aclient.base_url,
|
||||
"auth": self._aclient.auth,
|
||||
"event_hooks": self._aclient.event_hooks,
|
||||
"cookies": self._aclient.cookies,
|
||||
"max_redirects": self._aclient.max_redirects,
|
||||
"params": self._aclient.params,
|
||||
"trust_env": self._aclient.trust_env,
|
||||
}
|
||||
self._thread_pool = ThreadPoolExecutor(
|
||||
max_workers=min(10, (os.cpu_count() or 1) + 4)
|
||||
)
|
||||
|
||||
@property
|
||||
def aclient(self) -> httpx.AsyncClient:
|
||||
if self._aclient is None:
|
||||
self._aclient = httpx.AsyncClient(**self._client_params)
|
||||
return self._aclient
|
||||
|
||||
def _run_sync(self, coro: Coroutine[Any, Any, T]) -> T:
|
||||
"""Run coroutine in a separate thread to avoid event loop issues"""
|
||||
|
||||
# force a new client for this thread/event loop
|
||||
original_client = self._aclient
|
||||
self._aclient = None
|
||||
|
||||
def run_coro() -> T:
|
||||
async def wrapped_coro() -> T:
|
||||
return await coro
|
||||
|
||||
return asyncio.run(wrapped_coro())
|
||||
|
||||
result = self._thread_pool.submit(run_coro).result()
|
||||
|
||||
# restore the original client
|
||||
self._aclient = original_client
|
||||
|
||||
return result
|
||||
|
||||
async def _get_default_project(self) -> str:
|
||||
response = await self.aclient.get(
|
||||
urljoin(str(self.base_url), "/api/v1/projects"), headers=self.headers
|
||||
)
|
||||
response.raise_for_status()
|
||||
projects = response.json()
|
||||
default_project = [p for p in projects if p.get("is_default")]
|
||||
return default_project[0]["id"]
|
||||
|
||||
async def _build_url(
|
||||
self, endpoint: str, extra_params: Optional[List[str]] = None
|
||||
) -> str:
|
||||
"""Helper method to build URLs with common query parameters."""
|
||||
url = urljoin(str(self.base_url), endpoint)
|
||||
|
||||
if not self.project_id:
|
||||
self.project_id = await self._get_default_project()
|
||||
|
||||
query_params = []
|
||||
if self.organization_id:
|
||||
query_params.append(f"organization_id={self.organization_id}")
|
||||
if self.project_id:
|
||||
query_params.append(f"project_id={self.project_id}")
|
||||
if extra_params:
|
||||
query_params.extend([p for p in extra_params if p is not None])
|
||||
|
||||
if query_params:
|
||||
url += "?" + "&".join(query_params)
|
||||
|
||||
return url
|
||||
|
||||
async def acreate_report(
|
||||
self,
|
||||
name: str,
|
||||
template_instructions: Optional[str] = None,
|
||||
template_text: Optional[str] = None,
|
||||
template_file: Optional[Union[str, tuple[str, bytes]]] = None,
|
||||
input_files: Optional[List[Union[str, tuple[str, bytes]]]] = None,
|
||||
existing_retriever_id: Optional[str] = None,
|
||||
) -> ReportClient:
|
||||
"""Create a new report asynchronously."""
|
||||
url = await self._build_url("/api/v1/reports/")
|
||||
open_files: List[io.BufferedReader] = []
|
||||
|
||||
data = {"name": name}
|
||||
if template_instructions:
|
||||
data["template_instructions"] = template_instructions
|
||||
if template_text:
|
||||
data["template_text"] = template_text
|
||||
if existing_retriever_id:
|
||||
data["existing_retriever_id"] = str(existing_retriever_id)
|
||||
|
||||
files: List[tuple[str, io.BufferedReader | bytes]] = []
|
||||
if template_file:
|
||||
if isinstance(template_file, str):
|
||||
open_files.append(open(template_file, "rb"))
|
||||
files.append(("template_file", open_files[-1]))
|
||||
else:
|
||||
files.append(("template_file", template_file[1]))
|
||||
|
||||
if input_files:
|
||||
for f in input_files:
|
||||
if isinstance(f, str):
|
||||
open_files.append(open(f, "rb"))
|
||||
files.append(("files", open_files[-1]))
|
||||
else:
|
||||
files.append(("files", f[1]))
|
||||
|
||||
response = await self.aclient.post(
|
||||
url, headers=self.headers, data=data, files=files
|
||||
)
|
||||
try:
|
||||
response.raise_for_status()
|
||||
report_id = response.json()["id"]
|
||||
return ReportClient(report_id, name, self)
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise ValueError(
|
||||
f"Failed to create report: {e.response.text}\nError Code: {e.response.status_code}"
|
||||
)
|
||||
finally:
|
||||
for open_file in open_files:
|
||||
open_file.close()
|
||||
|
||||
def create_report(
|
||||
self,
|
||||
name: str,
|
||||
template_instructions: Optional[str] = None,
|
||||
template_text: Optional[str] = None,
|
||||
template_file: Optional[Union[str, tuple[str, bytes]]] = None,
|
||||
input_files: Optional[List[Union[str, tuple[str, bytes]]]] = None,
|
||||
existing_retriever_id: Optional[str] = None,
|
||||
) -> ReportClient:
|
||||
"""Create a new report."""
|
||||
return self._run_sync(
|
||||
self.acreate_report(
|
||||
name=name,
|
||||
template_instructions=template_instructions,
|
||||
template_text=template_text,
|
||||
template_file=template_file,
|
||||
input_files=input_files,
|
||||
existing_retriever_id=existing_retriever_id,
|
||||
)
|
||||
)
|
||||
|
||||
async def alist_reports(
|
||||
self, state: Optional[str] = None, limit: int = 100, offset: int = 0
|
||||
) -> List[ReportClient]:
|
||||
"""List all reports asynchronously."""
|
||||
params = []
|
||||
if state:
|
||||
params.append(f"state={state}")
|
||||
if limit:
|
||||
params.append(f"limit={limit}")
|
||||
if offset:
|
||||
params.append(f"offset={offset}")
|
||||
|
||||
url = await self._build_url(
|
||||
"/api/v1/reports/list",
|
||||
extra_params=params,
|
||||
)
|
||||
|
||||
response = await self.aclient.get(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
return [
|
||||
ReportClient(r["report_id"], r["name"], self)
|
||||
for r in data["report_responses"]
|
||||
]
|
||||
|
||||
def list_reports(
|
||||
self, state: Optional[str] = None, limit: int = 100, offset: int = 0
|
||||
) -> List[ReportClient]:
|
||||
"""Synchronous wrapper for listing reports."""
|
||||
return self._run_sync(self.alist_reports(state, limit, offset))
|
||||
|
||||
async def aget_report(self, report_id: str) -> ReportClient:
|
||||
"""Get a Report instance for working with a specific report."""
|
||||
url = await self._build_url(f"/api/v1/reports/{report_id}")
|
||||
|
||||
response = await self.aclient.get(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
return ReportClient(data["report_id"], data["name"], self)
|
||||
|
||||
def get_report(self, report_id: str) -> ReportClient:
|
||||
"""Synchronous wrapper for getting a report."""
|
||||
return self._run_sync(self.aget_report(report_id))
|
||||
|
||||
async def aget_report_metadata(self, report_id: str) -> ReportMetadata:
|
||||
"""Get metadata for a specific report asynchronously.
|
||||
|
||||
Returns:
|
||||
dict containing:
|
||||
- id: Report ID
|
||||
- name: Report name
|
||||
- state: Current report state
|
||||
- report_metadata: Additional metadata
|
||||
- template_file: Name of template file if used
|
||||
- template_instructions: Template instructions if provided
|
||||
- input_files: List of input file names
|
||||
"""
|
||||
url = await self._build_url(f"/api/v1/reports/{report_id}/metadata")
|
||||
|
||||
response = await self.aclient.get(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
return ReportMetadata(**response.json())
|
||||
|
||||
def get_report_metadata(self, report_id: str) -> ReportMetadata:
|
||||
"""Synchronous wrapper for getting report metadata."""
|
||||
return self._run_sync(self.aget_report_metadata(report_id))
|
||||
|
||||
async def adelete_report(self, report_id: str) -> None:
|
||||
"""Delete a specific report asynchronously."""
|
||||
url = await self._build_url(f"/api/v1/reports/{report_id}")
|
||||
|
||||
response = await self.aclient.delete(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
|
||||
def delete_report(self, report_id: str) -> None:
|
||||
"""Synchronous wrapper for deleting a report."""
|
||||
return self._run_sync(self.adelete_report(report_id))
|
||||
@@ -1,527 +0,0 @@
|
||||
import asyncio
|
||||
import httpx
|
||||
import time
|
||||
from typing import Optional, List, Literal, Union, TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from llama_cloud.types import (
|
||||
ReportEventItemEventData_Progress,
|
||||
ReportMetadata,
|
||||
EditSuggestion,
|
||||
ReportResponse,
|
||||
ReportPlan,
|
||||
ReportBlock,
|
||||
ReportPlanBlock,
|
||||
Report,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from llama_cloud_services.report.base import LlamaReport
|
||||
|
||||
|
||||
class MessageRole(str, Enum):
|
||||
USER = "user"
|
||||
ASSISTANT = "assistant"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
role: MessageRole
|
||||
content: str
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class EditAction:
|
||||
block_idx: int
|
||||
old_content: str
|
||||
new_content: Optional[str]
|
||||
action: Literal["approved", "rejected"]
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
DEFAULT_POLL_INTERVAL = 5
|
||||
DEFAULT_TIMEOUT = 600
|
||||
|
||||
|
||||
class ReportClient:
|
||||
"""Client for operations on a specific report."""
|
||||
|
||||
def __init__(self, report_id: str, name: str, parent_client: "LlamaReport"):
|
||||
self.report_id = report_id
|
||||
self.name = name
|
||||
self._client = parent_client
|
||||
self._headers = parent_client.headers
|
||||
self._run_sync = parent_client._run_sync
|
||||
self._build_url = parent_client._build_url
|
||||
self.chat_history: List[Message] = []
|
||||
self.edit_history: List[EditAction] = []
|
||||
|
||||
@property
|
||||
def aclient(self) -> httpx.AsyncClient:
|
||||
return self._client.aclient
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Report(id={self.report_id}, name={self.name})"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Report(id={self.report_id}, name={self.name})"
|
||||
|
||||
def _get_block_content(self, block: Union[ReportBlock, ReportPlanBlock]) -> str:
|
||||
if isinstance(block, ReportBlock):
|
||||
return block.template
|
||||
elif isinstance(block, ReportPlanBlock):
|
||||
return block.block.template
|
||||
else:
|
||||
raise ValueError(f"Invalid block type: {type(block)}")
|
||||
|
||||
def _get_block_idx(self, block: Union[ReportBlock, ReportPlanBlock]) -> int:
|
||||
if isinstance(block, ReportBlock):
|
||||
return block.idx
|
||||
elif isinstance(block, ReportPlanBlock):
|
||||
return block.block.idx
|
||||
else:
|
||||
raise ValueError(f"Invalid block type: {type(block)}")
|
||||
|
||||
async def aget(self, version: Optional[int] = None) -> ReportResponse:
|
||||
"""Get this report's details asynchronously."""
|
||||
extra_params = []
|
||||
if version is not None:
|
||||
extra_params.append(f"version={version}")
|
||||
|
||||
url = await self._build_url(f"/api/v1/reports/{self.report_id}", extra_params)
|
||||
|
||||
response = await self.aclient.get(url, headers=self._headers)
|
||||
response.raise_for_status()
|
||||
return ReportResponse(**response.json())
|
||||
|
||||
def get(self, version: Optional[int] = None) -> ReportResponse:
|
||||
"""Synchronous wrapper for getting this report's details."""
|
||||
return self._run_sync(self.aget(version))
|
||||
|
||||
async def aupdate_report(self, updated_report: Report) -> ReportResponse:
|
||||
"""Update this report's content asynchronously."""
|
||||
url = await self._build_url(f"/api/v1/reports/{self.report_id}")
|
||||
response = await self.aclient.patch(
|
||||
url, headers=self._headers, json={"content": updated_report.dict()}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return ReportResponse(**response.json())
|
||||
|
||||
def update_report(self, updated_report: Report) -> ReportResponse:
|
||||
"""Synchronous wrapper for updating this report's content."""
|
||||
return self._run_sync(self.aupdate_report(updated_report))
|
||||
|
||||
async def aupdate_plan(
|
||||
self,
|
||||
action: Literal["approve", "reject", "edit"],
|
||||
updated_plan: Optional[ReportPlan] = None,
|
||||
) -> ReportResponse:
|
||||
"""Update this report's plan asynchronously."""
|
||||
if action == "edit" and not updated_plan:
|
||||
raise ValueError("updated_plan is required when action is 'edit'")
|
||||
|
||||
url = await self._build_url(
|
||||
f"/api/v1/reports/{self.report_id}/plan", [f"action={action}"]
|
||||
)
|
||||
|
||||
data = None
|
||||
if updated_plan is not None:
|
||||
plan_dict = updated_plan.dict()
|
||||
plan_dict.pop("generated_at", None)
|
||||
data = plan_dict
|
||||
|
||||
if updated_plan is None and action == "edit":
|
||||
raise ValueError("updated_plan is required when action is 'edit'")
|
||||
|
||||
response = await self.aclient.patch(url, headers=self._headers, json=data)
|
||||
response.raise_for_status()
|
||||
return ReportResponse(**response.json())
|
||||
|
||||
def update_plan(
|
||||
self,
|
||||
action: Literal["approve", "reject", "edit"],
|
||||
updated_plan: Optional[ReportPlan] = None,
|
||||
) -> ReportResponse:
|
||||
"""Synchronous wrapper for updating this report's plan."""
|
||||
return self._run_sync(self.aupdate_plan(action, updated_plan))
|
||||
|
||||
async def asuggest_edits(
|
||||
self,
|
||||
user_query: str,
|
||||
auto_history: bool = True,
|
||||
chat_history: Optional[List[dict]] = None,
|
||||
) -> List[EditSuggestion]:
|
||||
"""Get AI suggestions for edits to this report asynchronously.
|
||||
|
||||
Args:
|
||||
user_query: The user's request/question about what to edit
|
||||
auto_history: Whether to automatically add the user's message to the chat history
|
||||
chat_history:
|
||||
A list of chat messages to include in the chat history.
|
||||
The format being a list of dictionaries with "role" and "content" keys.
|
||||
"""
|
||||
# Add user message to history
|
||||
self.chat_history.append(
|
||||
Message(role=MessageRole.USER, content=user_query, timestamp=datetime.now())
|
||||
)
|
||||
|
||||
# Format chat history with edit summaries
|
||||
chat_history_dicts = []
|
||||
for msg in self.chat_history[:-1]: # Exclude current message
|
||||
content = msg.content
|
||||
if msg.role == MessageRole.USER:
|
||||
# Add edit summary for user messages
|
||||
edit_summary = self._get_edit_summary_after_message(msg.timestamp)
|
||||
if edit_summary:
|
||||
content = f"{content}\n\nActions taken:\n{edit_summary}"
|
||||
|
||||
chat_history_dicts.append({"role": msg.role.value, "content": content})
|
||||
|
||||
# decide whether to include chat history or not
|
||||
if chat_history:
|
||||
chat_history_dicts = chat_history
|
||||
elif auto_history:
|
||||
chat_history_dicts = chat_history_dicts
|
||||
else:
|
||||
chat_history_dicts = []
|
||||
|
||||
# Make the API call
|
||||
url = await self._build_url(f"/api/v1/reports/{self.report_id}/suggest_edits")
|
||||
data = {"user_query": user_query, "chat_history": chat_history_dicts}
|
||||
|
||||
response = await self.aclient.post(url, headers=self._headers, json=data)
|
||||
response.raise_for_status()
|
||||
suggestions = response.json()
|
||||
suggestions = [EditSuggestion(**suggestion) for suggestion in suggestions]
|
||||
|
||||
# Add assistant response to history
|
||||
if suggestions:
|
||||
for suggestion in suggestions:
|
||||
self.chat_history.append(
|
||||
Message(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content=suggestion.justification,
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
)
|
||||
|
||||
return suggestions
|
||||
|
||||
def suggest_edits(
|
||||
self,
|
||||
user_query: str,
|
||||
auto_history: bool = True,
|
||||
chat_history: Optional[List[dict]] = None,
|
||||
) -> List[EditSuggestion]:
|
||||
"""Synchronous wrapper for getting edit suggestions."""
|
||||
return self._run_sync(
|
||||
self.asuggest_edits(user_query, auto_history, chat_history)
|
||||
)
|
||||
|
||||
async def await_completion(
|
||||
self, timeout: int = DEFAULT_TIMEOUT, poll_interval: int = DEFAULT_POLL_INTERVAL
|
||||
) -> Report:
|
||||
"""Wait for this report to complete processing."""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
report_response = await self.aget()
|
||||
status = report_response.status
|
||||
|
||||
if status == "completed":
|
||||
return report_response.report
|
||||
elif status == "error":
|
||||
events = await self.aget_events()
|
||||
raise ValueError(f"Report entered error state: {events[-1].msg}")
|
||||
elif time.time() - start_time > timeout:
|
||||
raise TimeoutError(f"Report did not complete within {timeout} seconds")
|
||||
|
||||
await asyncio.sleep(poll_interval)
|
||||
|
||||
def wait_for_completion(
|
||||
self, timeout: int = DEFAULT_TIMEOUT, poll_interval: int = DEFAULT_POLL_INTERVAL
|
||||
) -> Report:
|
||||
"""Synchronous wrapper for awaiting report completion."""
|
||||
return self._run_sync(self.await_completion(timeout, poll_interval))
|
||||
|
||||
async def await_for_plan(
|
||||
self, timeout: int = DEFAULT_TIMEOUT, poll_interval: int = DEFAULT_POLL_INTERVAL
|
||||
) -> ReportPlan:
|
||||
"""Wait for this report's plan to be ready for review."""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
report_metadata = await self.aget_metadata()
|
||||
state = report_metadata.state
|
||||
|
||||
if state == "waiting_approval":
|
||||
report_response = await self.aget()
|
||||
return report_response.plan
|
||||
elif state == "error":
|
||||
events = await self.aget_events()
|
||||
raise ValueError(f"Report entered error state: {events[-1].msg}")
|
||||
elif time.time() - start_time > timeout:
|
||||
raise TimeoutError(f"Plan was not ready within {timeout} seconds")
|
||||
|
||||
await asyncio.sleep(poll_interval)
|
||||
|
||||
def wait_for_plan(
|
||||
self, timeout: int = DEFAULT_TIMEOUT, poll_interval: int = DEFAULT_POLL_INTERVAL
|
||||
) -> ReportPlan:
|
||||
"""Synchronous wrapper for awaiting plan readiness."""
|
||||
return self._run_sync(self.await_for_plan(timeout, poll_interval))
|
||||
|
||||
async def aget_metadata(self) -> ReportMetadata:
|
||||
"""Get this report's metadata asynchronously."""
|
||||
return await self._client.aget_report_metadata(self.report_id)
|
||||
|
||||
def get_metadata(self) -> ReportMetadata:
|
||||
"""Synchronous wrapper for getting this report's metadata."""
|
||||
return self._run_sync(self.aget_metadata())
|
||||
|
||||
async def adelete(self) -> None:
|
||||
"""Delete this report asynchronously."""
|
||||
return await self._client.adelete_report(self.report_id)
|
||||
|
||||
def delete(self) -> None:
|
||||
"""Synchronous wrapper for deleting this report."""
|
||||
return self._run_sync(self.adelete())
|
||||
|
||||
async def aaccept_edit(self, suggestion: EditSuggestion) -> None:
|
||||
"""Accept a suggested edit.
|
||||
|
||||
Args:
|
||||
suggestion: The EditSuggestion to accept, typically from suggest_edits()
|
||||
"""
|
||||
if len(suggestion.blocks) == 0:
|
||||
return
|
||||
|
||||
# Determine if we're editing a plan or report based on first block type
|
||||
is_plan_edit = isinstance(suggestion.blocks[0], ReportPlanBlock)
|
||||
|
||||
# Get current content
|
||||
report_response = await self.aget()
|
||||
current_blocks = (
|
||||
report_response.plan.blocks
|
||||
if is_plan_edit
|
||||
else report_response.report.blocks
|
||||
)
|
||||
|
||||
# Track the edit
|
||||
new_blocks = []
|
||||
for edit_block in suggestion.blocks:
|
||||
# Find matching block in current content
|
||||
old_block = next(
|
||||
(
|
||||
b
|
||||
for b in current_blocks
|
||||
if self._get_block_idx(b) == self._get_block_idx(edit_block)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
old_content = (
|
||||
self._get_block_content(old_block) if old_block else "[No old content]"
|
||||
)
|
||||
new_content = self._get_block_content(edit_block)
|
||||
|
||||
if is_plan_edit:
|
||||
new_queries_str = "\n".join(
|
||||
[
|
||||
f"Field: {q.field}, Prompt: {q.prompt}, Context: {q.context}"
|
||||
for q in edit_block.queries
|
||||
]
|
||||
)
|
||||
new_dependency_str = (
|
||||
f"Depends on: {edit_block.dependency}"
|
||||
if edit_block.dependency
|
||||
else ""
|
||||
)
|
||||
new_content += f"\n\n{new_queries_str}\n{new_dependency_str}"
|
||||
|
||||
if old_block:
|
||||
old_queries_str = "\n".join(
|
||||
[
|
||||
f"Field: {q.field}, Prompt: {q.prompt}, Context: {q.context}"
|
||||
for q in old_block.queries
|
||||
]
|
||||
)
|
||||
old_dependency_str = (
|
||||
f"Depends on: {old_block.dependency}"
|
||||
if old_block.dependency
|
||||
else ""
|
||||
)
|
||||
old_content += f"\n\n{old_queries_str}\n{old_dependency_str}"
|
||||
|
||||
self.edit_history.append(
|
||||
EditAction(
|
||||
block_idx=self._get_block_idx(edit_block),
|
||||
old_content=old_content,
|
||||
new_content=new_content,
|
||||
action="approved",
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
)
|
||||
|
||||
# Create updated block
|
||||
if is_plan_edit:
|
||||
new_blocks.append(
|
||||
ReportPlanBlock(
|
||||
block=ReportBlock(
|
||||
idx=edit_block.block.idx,
|
||||
template=self._get_block_content(edit_block),
|
||||
sources=edit_block.block.sources,
|
||||
),
|
||||
queries=edit_block.queries,
|
||||
dependency=edit_block.dependency,
|
||||
)
|
||||
)
|
||||
else:
|
||||
new_blocks.append(
|
||||
ReportBlock(
|
||||
idx=edit_block.idx,
|
||||
template=self._get_block_content(edit_block),
|
||||
sources=edit_block.sources,
|
||||
)
|
||||
)
|
||||
|
||||
if new_blocks:
|
||||
if is_plan_edit:
|
||||
# Update plan in place
|
||||
plan = report_response.plan
|
||||
|
||||
# Replace edited blocks and add new ones
|
||||
for new_block in new_blocks:
|
||||
block_idx = self._get_block_idx(new_block)
|
||||
existing_block_idx = next(
|
||||
(
|
||||
i
|
||||
for i, b in enumerate(plan.blocks)
|
||||
if b.block.idx == block_idx
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if existing_block_idx is not None:
|
||||
# Replace existing block
|
||||
plan.blocks[existing_block_idx] = new_block
|
||||
else:
|
||||
# Add new block to end
|
||||
plan.blocks.append(new_block)
|
||||
|
||||
await self.aupdate_plan("edit", plan)
|
||||
else:
|
||||
# Update report in place
|
||||
report = report_response.report
|
||||
|
||||
# Replace edited blocks and add new ones
|
||||
for new_block in new_blocks:
|
||||
block_idx = self._get_block_idx(new_block)
|
||||
existing_block_idx = next(
|
||||
(i for i, b in enumerate(report.blocks) if b.idx == block_idx),
|
||||
None,
|
||||
)
|
||||
|
||||
if existing_block_idx is not None:
|
||||
# Replace existing block
|
||||
report.blocks[existing_block_idx] = new_block
|
||||
else:
|
||||
# Add new block to end
|
||||
report.blocks.append(new_block)
|
||||
|
||||
await self.aupdate_report(report)
|
||||
|
||||
def accept_edit(self, suggestion: EditSuggestion) -> None:
|
||||
"""Synchronous wrapper for accepting an edit."""
|
||||
return self._run_sync(self.aaccept_edit(suggestion))
|
||||
|
||||
async def areject_edit(self, suggestion: EditSuggestion) -> None:
|
||||
"""Reject a suggested edit.
|
||||
|
||||
Args:
|
||||
suggestion: The EditSuggestion to reject, typically from suggest_edits()
|
||||
"""
|
||||
# Track the rejections
|
||||
for edit_block in suggestion.blocks:
|
||||
self.edit_history.append(
|
||||
EditAction(
|
||||
block_idx=self._get_block_idx(edit_block),
|
||||
old_content=self._get_block_content(edit_block),
|
||||
new_content=None,
|
||||
action="rejected",
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
)
|
||||
|
||||
def reject_edit(self, suggestion: EditSuggestion) -> None:
|
||||
"""Synchronous wrapper for rejecting an edit."""
|
||||
return self._run_sync(self.areject_edit(suggestion))
|
||||
|
||||
def _get_edit_summary_after_message(
|
||||
self, message_timestamp: datetime
|
||||
) -> Optional[str]:
|
||||
"""Get a summary of edits that occurred after a specific message."""
|
||||
relevant_edits = [
|
||||
edit for edit in self.edit_history if edit.timestamp > message_timestamp
|
||||
]
|
||||
|
||||
if not relevant_edits:
|
||||
return None
|
||||
|
||||
approved = [edit for edit in relevant_edits if edit.action == "approved"]
|
||||
rejected = [edit for edit in relevant_edits if edit.action == "rejected"]
|
||||
|
||||
summary = []
|
||||
|
||||
if approved:
|
||||
summary.append("Approved edits:")
|
||||
for edit in approved:
|
||||
summary.append(
|
||||
f'Block {edit.block_idx}: "{edit.old_content}" -> "{edit.new_content}"'
|
||||
)
|
||||
|
||||
if rejected:
|
||||
if approved: # Add spacing if we had approved edits
|
||||
summary.append("")
|
||||
summary.append("Rejected edits:")
|
||||
for edit in rejected:
|
||||
summary.append(f'Block {edit.block_idx}: "{edit.old_content}"')
|
||||
|
||||
return "\n".join(summary)
|
||||
|
||||
async def aget_events(
|
||||
self, last_sequence: Optional[int] = None
|
||||
) -> List[ReportEventItemEventData_Progress]:
|
||||
"""Get all events for this report asynchronously.
|
||||
|
||||
Args:
|
||||
last_sequence: If provided, only get events after this sequence number
|
||||
|
||||
Returns:
|
||||
List of ReportEvent objects
|
||||
"""
|
||||
extra_params = []
|
||||
if last_sequence is not None:
|
||||
extra_params.append(f"last_sequence={last_sequence}")
|
||||
|
||||
url = await self._build_url(
|
||||
f"/api/v1/reports/{self.report_id}/events", extra_params
|
||||
)
|
||||
|
||||
response = await self.aclient.get(url, headers=self._headers)
|
||||
response.raise_for_status()
|
||||
progress_events = []
|
||||
for event in response.json():
|
||||
if event["event_type"] == "progress":
|
||||
progress_events.append(
|
||||
ReportEventItemEventData_Progress(**event["event_data"])
|
||||
)
|
||||
|
||||
return progress_events
|
||||
|
||||
def get_events(
|
||||
self, last_sequence: Optional[int] = None
|
||||
) -> List[ReportEventItemEventData_Progress]:
|
||||
"""Synchronous wrapper for getting report events."""
|
||||
return self._run_sync(self.aget_events(last_sequence))
|
||||
@@ -11,13 +11,13 @@ dev = [
|
||||
|
||||
[project]
|
||||
name = "llama-parse"
|
||||
version = "0.6.62"
|
||||
version = "0.6.68"
|
||||
description = "Parse files into RAG-Optimized formats."
|
||||
authors = [{name = "Logan Markewich", email = "logan@llamaindex.ai"}]
|
||||
requires-python = ">=3.9,<4.0"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
dependencies = ["llama-cloud-services>=0.6.62"]
|
||||
dependencies = ["llama-cloud-services>=0.6.68"]
|
||||
|
||||
[project.scripts]
|
||||
llama-parse = "llama_parse.cli.main:parse"
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ dev = [
|
||||
|
||||
[project]
|
||||
name = "llama-cloud-services"
|
||||
version = "0.6.62"
|
||||
version = "0.6.68"
|
||||
description = "Tailored SDK clients for LlamaCloud services."
|
||||
authors = [{name = "Logan Markewich", email = "logan@runllama.ai"}]
|
||||
requires-python = ">=3.9,<4.0"
|
||||
@@ -27,7 +27,7 @@ readme = "README.md"
|
||||
license = "MIT"
|
||||
dependencies = [
|
||||
"llama-index-core>=0.12.0",
|
||||
"llama-cloud==0.1.37",
|
||||
"llama-cloud==0.1.42",
|
||||
"pydantic>=2.8,!=2.10",
|
||||
"click>=8.1.7,<9",
|
||||
"python-dotenv>=1.0.1,<2",
|
||||
|
||||
@@ -68,7 +68,7 @@ async def test_agent_data_crud_operations():
|
||||
client=client,
|
||||
type=ExampleData,
|
||||
collection=f"test-collection-{test_id[:8]}",
|
||||
agent_url_id=LLAMA_DEPLOY_DEPLOYMENT_NAME,
|
||||
deployment_name=LLAMA_DEPLOY_DEPLOYMENT_NAME,
|
||||
)
|
||||
|
||||
# Create test data
|
||||
|
||||
@@ -2,6 +2,7 @@ import os
|
||||
import pytest
|
||||
from llama_cloud.client import AsyncLlamaCloud
|
||||
from llama_cloud.types import Project, ClassifierRule, ClassifyJobResults
|
||||
from llama_cloud_services.beta.classifier.types import ClassifyJobResultsWithFiles
|
||||
from llama_cloud_services.beta.classifier.client import ClassifyClient
|
||||
from llama_cloud_services.files.client import FileClient
|
||||
from llama_cloud.errors.unprocessable_entity_error import UnprocessableEntityError
|
||||
@@ -130,6 +131,44 @@ async def test_classify_file_ids(
|
||||
assert item.result.type == expected_type
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_classify_file_ids_from_api_key(
|
||||
e2e_test_settings: EndToEndTestSettings,
|
||||
file_client: FileClient,
|
||||
simple_pdf_file_path: str,
|
||||
research_paper_path: str,
|
||||
classification_rules: list[ClassifierRule],
|
||||
):
|
||||
"""Test classifying files by their IDs"""
|
||||
# Upload test files first to get their IDs
|
||||
pdf_file = await file_client.upload_file(simple_pdf_file_path)
|
||||
research_paper_file = await file_client.upload_file(research_paper_path)
|
||||
|
||||
classify_client = ClassifyClient.from_api_key(
|
||||
api_key=e2e_test_settings.LLAMA_CLOUD_API_KEY.get_secret_value(),
|
||||
base_url=e2e_test_settings.LLAMA_CLOUD_BASE_URL,
|
||||
project_id=pdf_file.project_id,
|
||||
organization_id=e2e_test_settings.LLAMA_CLOUD_ORGANIZATION_ID,
|
||||
)
|
||||
|
||||
# Classify the uploaded files
|
||||
results = await classify_client.aclassify_file_ids(
|
||||
rules=classification_rules, file_ids=[pdf_file.id, research_paper_file.id]
|
||||
)
|
||||
|
||||
assert isinstance(results, ClassifyJobResults)
|
||||
assert len(results.items) == 2
|
||||
|
||||
file_id_to_expected_type = {
|
||||
pdf_file.id: "number",
|
||||
research_paper_file.id: "research_paper",
|
||||
}
|
||||
# Verify each file got classified
|
||||
for item in results.items:
|
||||
expected_type = file_id_to_expected_type[item.file_id]
|
||||
assert item.result.type == expected_type
|
||||
|
||||
|
||||
@parameterize_sync_and_async
|
||||
@pytest.mark.asyncio
|
||||
async def test_classify_file_path(
|
||||
@@ -149,7 +188,7 @@ async def test_classify_file_path(
|
||||
rules=classification_rules, file_input_path=simple_pdf_file_path
|
||||
)
|
||||
|
||||
assert isinstance(results, ClassifyJobResults)
|
||||
assert isinstance(results, ClassifyJobResultsWithFiles)
|
||||
assert len(results.items) == 1
|
||||
|
||||
# Verify the file got classified
|
||||
@@ -180,7 +219,7 @@ async def test_classify_file_paths(
|
||||
file_input_paths=[simple_pdf_file_path, research_paper_path],
|
||||
)
|
||||
|
||||
assert isinstance(results, ClassifyJobResults)
|
||||
assert isinstance(results, ClassifyJobResultsWithFiles)
|
||||
assert len(results.items) == 2
|
||||
|
||||
file_name_to_expected_type = {
|
||||
@@ -189,8 +228,7 @@ async def test_classify_file_paths(
|
||||
}
|
||||
# Verify each file got classified
|
||||
for item in results.items:
|
||||
file = await file_client.get_file(item.file_id)
|
||||
expected_type = file_name_to_expected_type[file.name]
|
||||
expected_type = file_name_to_expected_type[item.file.name]
|
||||
assert item.result.type == expected_type
|
||||
|
||||
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import os
|
||||
import pytest
|
||||
import uuid
|
||||
from typing import AsyncGenerator
|
||||
from pytest_asyncio import fixture as async_fixture
|
||||
from llama_cloud_services.report import LlamaReport, ReportClient
|
||||
|
||||
# Skip tests if no API key is set
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not os.getenv("LLAMA_CLOUD_API_KEY") or os.getenv("CI") == "true",
|
||||
reason="No API key provided",
|
||||
)
|
||||
|
||||
|
||||
@async_fixture(scope="function")
|
||||
async def client() -> AsyncGenerator[LlamaReport, None]:
|
||||
"""Create a LlamaReport client."""
|
||||
client = LlamaReport()
|
||||
reports_before = await client.alist_reports()
|
||||
reports_before_ids = [r.report_id for r in reports_before]
|
||||
try:
|
||||
yield client
|
||||
finally:
|
||||
# clean up reports
|
||||
try:
|
||||
reports_after = await client.alist_reports()
|
||||
reports_after_ids = [r.report_id for r in reports_after]
|
||||
for report_id in reports_before_ids:
|
||||
if report_id not in reports_after_ids:
|
||||
await client.adelete_report(report_id)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
await client.aclient.aclose()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def unique_name() -> str:
|
||||
"""Generate a unique report name."""
|
||||
return f"test-report-{uuid.uuid4()}"
|
||||
|
||||
|
||||
@async_fixture(scope="function")
|
||||
async def report(
|
||||
client: LlamaReport, unique_name: str
|
||||
) -> AsyncGenerator[ReportClient, None]:
|
||||
"""Create a report."""
|
||||
report = await client.acreate_report(
|
||||
name=unique_name,
|
||||
template_text=(
|
||||
"# [Some title]\n\n"
|
||||
" ## TLDR\n"
|
||||
"A quick summary of the paper.\n\n"
|
||||
"## Details\n"
|
||||
"More details about the paper, possible more than one section here.\n"
|
||||
),
|
||||
input_files=["tests/test_files/paper.md"],
|
||||
)
|
||||
try:
|
||||
yield report
|
||||
finally:
|
||||
await report.adelete()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.xfail(
|
||||
condition=lambda: os.getenv("CI"),
|
||||
reason="Backend db issues; needs to be fixed.",
|
||||
)
|
||||
async def test_create_and_delete_report(
|
||||
client: LlamaReport, report: ReportClient
|
||||
) -> None:
|
||||
"""Test basic report creation and deletion."""
|
||||
# Verify the report exists
|
||||
metadata = await report.aget_metadata()
|
||||
assert metadata.name == report.name
|
||||
|
||||
# Test listing reports
|
||||
reports = await client.alist_reports()
|
||||
assert any(r.report_id == report.report_id for r in reports)
|
||||
|
||||
# Test getting report by ID
|
||||
fetched_report = await client.aget_report(report.report_id)
|
||||
assert fetched_report.report_id == report.report_id
|
||||
assert fetched_report.name == report.name
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.xfail(
|
||||
condition=lambda: os.getenv("CI"),
|
||||
reason="Report plan sometimes times out",
|
||||
raises=TimeoutError,
|
||||
)
|
||||
async def test_report_plan_workflow(report: ReportClient) -> None:
|
||||
"""Test the report planning workflow."""
|
||||
# Wait for the plan
|
||||
plan = await report.await_for_plan()
|
||||
assert plan is not None
|
||||
|
||||
# Approve the plan
|
||||
response = await report.aupdate_plan(action="approve")
|
||||
assert response is not None
|
||||
|
||||
# Wait for completion
|
||||
completed_report = await report.await_completion()
|
||||
assert len(completed_report.blocks) > 0
|
||||
|
||||
# Get edit suggestions
|
||||
suggestions = await report.asuggest_edits(
|
||||
"TLDR section header more formal.", auto_history=True
|
||||
)
|
||||
assert len(suggestions) > 0
|
||||
|
||||
# Test accepting an edit
|
||||
await report.aaccept_edit(suggestions[0])
|
||||
|
||||
# Get more suggestions and test rejecting
|
||||
more_suggestions = await report.asuggest_edits(
|
||||
"Add a section about machine learning.", auto_history=True
|
||||
)
|
||||
assert len(more_suggestions) > 0
|
||||
await report.areject_edit(more_suggestions[0])
|
||||
|
||||
# Verify chat history is maintained
|
||||
assert len(report.chat_history) >= 4 # 2 user messages + 2 assistant responses
|
||||
|
||||
# get events
|
||||
events = await report.aget_events()
|
||||
assert len(events) > 0
|
||||
@@ -0,0 +1,158 @@
|
||||
import pytest
|
||||
from typing import Any, Dict, List, Optional
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
|
||||
from llama_cloud.types.agent_data import AgentData
|
||||
from llama_cloud.types.aggregate_group import AggregateGroup
|
||||
|
||||
from llama_cloud_services.beta.agent_data.client import AsyncAgentDataClient
|
||||
|
||||
|
||||
class Person(BaseModel):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
|
||||
class FakeBeta:
|
||||
def __init__(self) -> None:
|
||||
self._get_item_response: Optional[AgentData] = None
|
||||
self._search_items: List[AgentData] = []
|
||||
self._aggregate_items: List[AggregateGroup] = []
|
||||
self._total_size: Optional[int] = None
|
||||
self._next_page_token: Optional[str] = None
|
||||
|
||||
# Single get
|
||||
async def get_agent_data(self, item_id: str) -> AgentData:
|
||||
assert self._get_item_response is not None, "_get_item_response not set"
|
||||
return self._get_item_response
|
||||
|
||||
# Search
|
||||
async def search_agent_data_api_v_1_beta_agent_data_search_post(
|
||||
self,
|
||||
*,
|
||||
deployment_name: str,
|
||||
collection: str,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
order_by: Optional[str] = None,
|
||||
offset: Optional[int] = None,
|
||||
page_size: Optional[int] = None,
|
||||
include_total: bool = False,
|
||||
) -> Any:
|
||||
class Resp:
|
||||
def __init__(
|
||||
self,
|
||||
items: List[AgentData],
|
||||
total_size: Optional[int],
|
||||
next_page_token: Optional[str],
|
||||
) -> None:
|
||||
self.items = items
|
||||
self.total_size = total_size
|
||||
self.next_page_token = next_page_token
|
||||
|
||||
return Resp(self._search_items, self._total_size, self._next_page_token)
|
||||
|
||||
# Aggregate
|
||||
async def aggregate_agent_data_api_v_1_beta_agent_data_aggregate_post(
|
||||
self,
|
||||
*,
|
||||
deployment_name: str,
|
||||
collection: str,
|
||||
page_size: Optional[int] = None,
|
||||
filter: Optional[Dict[str, Any]] = None,
|
||||
order_by: Optional[str] = None,
|
||||
group_by: Optional[List[str]] = None,
|
||||
count: Optional[bool] = None,
|
||||
first: Optional[bool] = None,
|
||||
offset: Optional[int] = None,
|
||||
) -> Any:
|
||||
class Resp:
|
||||
def __init__(
|
||||
self,
|
||||
items: List[AggregateGroup],
|
||||
total_size: Optional[int],
|
||||
next_page_token: Optional[str],
|
||||
) -> None:
|
||||
self.items = items
|
||||
self.total_size = total_size
|
||||
self.next_page_token = next_page_token
|
||||
|
||||
return Resp(self._aggregate_items, self._total_size, self._next_page_token)
|
||||
|
||||
|
||||
class FakeClient:
|
||||
def __init__(self) -> None:
|
||||
self.beta = FakeBeta()
|
||||
|
||||
|
||||
def make_agent_data(data: Dict[str, Any]) -> AgentData:
|
||||
return AgentData(
|
||||
id="id-1",
|
||||
deployment_name="dep",
|
||||
collection="col",
|
||||
data=data,
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
)
|
||||
|
||||
|
||||
def make_group(
|
||||
group_key: Dict[str, Any],
|
||||
first_item: Optional[Dict[str, Any]],
|
||||
count: Optional[int] = None,
|
||||
) -> AggregateGroup:
|
||||
return AggregateGroup(group_key=group_key, count=count, first_item=first_item)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_untyped_get_item_valid_to_dict() -> None:
|
||||
client = FakeClient()
|
||||
client.beta._get_item_response = make_agent_data({"name": "Alice", "age": 30})
|
||||
|
||||
adc = AsyncAgentDataClient(type=Person, client=client, deployment_name="dep")
|
||||
item = await adc.untyped_get_item("id-1")
|
||||
assert item.data == {"name": "Alice", "age": 30}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_untyped_get_item_invalid_retains_dict() -> None:
|
||||
client = FakeClient()
|
||||
# age wrong type; will fail validation and should be returned as dict
|
||||
client.beta._get_item_response = make_agent_data({"name": "Bob", "age": "x"})
|
||||
|
||||
adc = AsyncAgentDataClient(type=Person, client=client, deployment_name="dep")
|
||||
item = await adc.untyped_get_item("id-1")
|
||||
assert item.data == {"name": "Bob", "age": "x"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_untyped_search_mixed_items() -> None:
|
||||
client = FakeClient()
|
||||
client.beta._search_items = [
|
||||
make_agent_data({"name": "Carol", "age": 22}),
|
||||
make_agent_data({"name": "Dave", "age": "bad"}),
|
||||
]
|
||||
client.beta._total_size = 2
|
||||
|
||||
adc = AsyncAgentDataClient(type=Person, client=client, deployment_name="dep")
|
||||
results = await adc.untyped_search(include_total=True)
|
||||
assert len(results.items) == 2
|
||||
assert results.items[0].data == {"name": "Carol", "age": 22}
|
||||
assert results.items[1].data == {"name": "Dave", "age": "bad"}
|
||||
assert results.total_size == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_untyped_aggregate_first_item_dict() -> None:
|
||||
client = FakeClient()
|
||||
client.beta._aggregate_items = [
|
||||
make_group({"k": 1}, {"name": "Eve", "age": 40}),
|
||||
make_group({"k": 2}, {"name": "Frank", "age": "bad"}),
|
||||
]
|
||||
client.beta._total_size = 2
|
||||
|
||||
adc = AsyncAgentDataClient(type=Person, client=client, deployment_name="dep")
|
||||
results = await adc.untyped_aggregate(group_by=["k"], first=True)
|
||||
assert len(results.items) == 2
|
||||
assert results.items[0].first_item == {"name": "Eve", "age": 40}
|
||||
assert results.items[1].first_item == {"name": "Frank", "age": "bad"}
|
||||
@@ -38,7 +38,7 @@ def test_typed_agent_data_from_raw():
|
||||
"""Test TypedAgentData.from_raw class method."""
|
||||
raw_data = AgentData(
|
||||
id="456",
|
||||
agent_slug="extraction-agent",
|
||||
deployment_name="extraction-agent",
|
||||
collection="employees",
|
||||
data={"name": "Jane Smith", "age": 25, "email": "jane@company.com"},
|
||||
created_at=datetime.now(),
|
||||
@@ -48,7 +48,7 @@ def test_typed_agent_data_from_raw():
|
||||
typed_data = TypedAgentData.from_raw(raw_data, Person)
|
||||
|
||||
assert typed_data.id == "456"
|
||||
assert typed_data.agent_url_id == "extraction-agent"
|
||||
assert typed_data.deployment_name == "extraction-agent"
|
||||
assert typed_data.collection == "employees"
|
||||
assert typed_data.data.name == "Jane Smith"
|
||||
assert typed_data.data.age == 25
|
||||
@@ -56,10 +56,10 @@ def test_typed_agent_data_from_raw():
|
||||
|
||||
|
||||
def test_typed_agent_data_from_raw_validation_error():
|
||||
"""Test TypedAgentData.from_raw with invalid data."""
|
||||
"""Test TypedAgentData.from_raw with invalid data now raises InvalidTypedAgentData."""
|
||||
raw_data = AgentData(
|
||||
id="789",
|
||||
agent_slug="test-agent",
|
||||
deployment_name="test-agent",
|
||||
collection="people",
|
||||
data={"name": "Invalid Person", "age": "not_a_number"}, # Invalid age
|
||||
created_at=datetime.now(),
|
||||
|
||||
@@ -1,16 +1,155 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import llama_cloud_services.index.base as base
|
||||
from llama_cloud import (
|
||||
PipelineEmbeddingConfig_ManagedOpenaiEmbedding,
|
||||
Project,
|
||||
Pipeline,
|
||||
CloudDocument,
|
||||
)
|
||||
from llama_index.core.constants import DEFAULT_PROJECT_NAME
|
||||
from llama_index.core.indices.managed.base import BaseManagedIndex
|
||||
from llama_cloud_services.index import (
|
||||
LlamaCloudIndex,
|
||||
from llama_index.core.schema import Document
|
||||
from llama_cloud_services.index import LlamaCloudIndex
|
||||
|
||||
|
||||
# Simple test data as values, not fixtures
|
||||
TEST_PROJECT = Project(id="proj-123", name="test-project", organization_id="org-123")
|
||||
|
||||
EMBEDDING_CONFIG = PipelineEmbeddingConfig_ManagedOpenaiEmbedding(
|
||||
type="MANAGED_OPENAI_EMBEDDING"
|
||||
)
|
||||
TEST_PIPELINE = Pipeline(
|
||||
id="pipe-456",
|
||||
name="test-pipeline",
|
||||
project_id="proj-123",
|
||||
embedding_config=PipelineEmbeddingConfig_ManagedOpenaiEmbedding(
|
||||
type="MANAGED_OPENAI_EMBEDDING"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_class():
|
||||
@pytest.fixture
|
||||
def mock_client() -> MagicMock:
|
||||
"""Mock client with sensible defaults."""
|
||||
client = MagicMock()
|
||||
client.projects.upsert_project.return_value = Project(
|
||||
id="default-proj", name=DEFAULT_PROJECT_NAME, organization_id="default-org"
|
||||
)
|
||||
client.pipelines.upsert_pipeline.return_value = Pipeline(
|
||||
id="default-pipe",
|
||||
name="default",
|
||||
project_id="default-proj",
|
||||
embedding_config=EMBEDDING_CONFIG,
|
||||
)
|
||||
client.pipelines.upsert_batch_pipeline_documents.return_value = [
|
||||
CloudDocument(id="doc-1", text="test", metadata={})
|
||||
]
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def base_patches(mock_client: MagicMock) -> None:
|
||||
"""Auto-applied patches for all tests."""
|
||||
with (
|
||||
patch.object(base, "get_client", return_value=mock_client),
|
||||
patch.object(
|
||||
base,
|
||||
"resolve_project_and_pipeline",
|
||||
return_value=(TEST_PROJECT, TEST_PIPELINE),
|
||||
),
|
||||
patch.object(base.LlamaCloudIndex, "wait_for_completion"),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
def test_class() -> None:
|
||||
names_of_base_classes = [b.__name__ for b in LlamaCloudIndex.__mro__]
|
||||
assert BaseManagedIndex.__name__ in names_of_base_classes
|
||||
|
||||
|
||||
def test_conflicting_index_identifiers():
|
||||
def test_conflicting_index_identifiers() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
LlamaCloudIndex(name="test", pipeline_id="test", index_id="test")
|
||||
|
||||
|
||||
def test_from_documents_uses_provided_project_id(mock_client: MagicMock) -> None:
|
||||
provided_project_id = "proj-123"
|
||||
organization_id = "org-abc"
|
||||
index_name = "my_new_index"
|
||||
|
||||
# Override resolve to return project with provided ID
|
||||
test_project = Project(
|
||||
id=provided_project_id, name="my_project", organization_id=organization_id
|
||||
)
|
||||
test_pipeline = Pipeline(
|
||||
id="pipe-xyz",
|
||||
name=index_name,
|
||||
project_id=provided_project_id,
|
||||
embedding_config=EMBEDDING_CONFIG,
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
base, "resolve_project_and_pipeline", return_value=(test_project, test_pipeline)
|
||||
):
|
||||
docs = [Document(text="hello")]
|
||||
index = LlamaCloudIndex.from_documents(
|
||||
documents=docs,
|
||||
name=index_name,
|
||||
project_id=provided_project_id,
|
||||
)
|
||||
|
||||
# Assert - project upsert not called; pipeline uses provided project_id
|
||||
mock_client.projects.upsert_project.assert_not_called()
|
||||
assert mock_client.pipelines.upsert_pipeline.call_count == 1
|
||||
assert (
|
||||
mock_client.pipelines.upsert_pipeline.call_args.kwargs["project_id"]
|
||||
== provided_project_id
|
||||
)
|
||||
assert index.project.id == provided_project_id
|
||||
|
||||
|
||||
def test_from_documents_upserts_project_when_project_id_missing(
|
||||
mock_client: MagicMock,
|
||||
) -> None:
|
||||
organization_id = "org-xyz"
|
||||
index_name = "my_new_index"
|
||||
|
||||
# Project is created when project_id is not provided
|
||||
upserted_project = Project(
|
||||
id="proj-999", name=DEFAULT_PROJECT_NAME, organization_id=organization_id
|
||||
)
|
||||
mock_client.projects.upsert_project.return_value = upserted_project
|
||||
|
||||
test_pipeline = Pipeline(
|
||||
id="pipe-xyz",
|
||||
name=index_name,
|
||||
project_id=upserted_project.id,
|
||||
embedding_config=EMBEDDING_CONFIG,
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
base,
|
||||
"resolve_project_and_pipeline",
|
||||
return_value=(upserted_project, test_pipeline),
|
||||
):
|
||||
docs = [Document(text="world")]
|
||||
index = LlamaCloudIndex.from_documents(
|
||||
documents=docs,
|
||||
name=index_name,
|
||||
organization_id=organization_id,
|
||||
)
|
||||
|
||||
# Assert - project was upserted with org id and default project name
|
||||
mock_client.projects.upsert_project.assert_called_once()
|
||||
kwargs = mock_client.projects.upsert_project.call_args.kwargs
|
||||
assert kwargs["organization_id"] == organization_id
|
||||
assert kwargs["request"].name == DEFAULT_PROJECT_NAME
|
||||
|
||||
# Pipeline created under the upserted project id
|
||||
assert (
|
||||
mock_client.pipelines.upsert_pipeline.call_args.kwargs["project_id"]
|
||||
== upserted_project.id
|
||||
)
|
||||
assert index.project.id == upserted_project.id
|
||||
|
||||
Generated
+5
-5
@@ -1582,21 +1582,21 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "llama-cloud"
|
||||
version = "0.1.37"
|
||||
version = "0.1.42"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/dd/c4f2516523778a0d4b284fa4b66a0136afdd59694f105102838cf793e675/llama_cloud-0.1.37.tar.gz", hash = "sha256:b6d62e7386d1aa85905b7e3f7c19a40694be54c1596668bceaa456cd84ede666", size = 108707, upload-time = "2025-08-04T22:00:37.18Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/04/ae0694b582d6aab4d6e7957febb7bff048897ac231ad80ba1bd71547d944/llama_cloud-0.1.42.tar.gz", hash = "sha256:485aa0e364ea648e3aaa3b2c54af7bcb6f2242c50b4f86ec022e137413fff464", size = 112480, upload-time = "2025-09-16T20:25:42.631Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/59/ca/a7f874b041d2566f000ecc88bf1b76819a8bdbbaea85036ca6905ae3f0f7/llama_cloud-0.1.37-py3-none-any.whl", hash = "sha256:3109ec74575f53311ec4957ca8aea2d6e30556a43d24c6316ceab91c4fed6ab7", size = 314244, upload-time = "2025-08-04T22:00:35.696Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/61/85d115699a59d03f0783e119aaf6d534fca95dbe1a4531a8056e6a4774ed/llama_cloud-0.1.42-py3-none-any.whl", hash = "sha256:4ed3edde4a277ff52eeb831188c8476eb079b5e4605ad3142157a0f054b27d96", size = 311857, upload-time = "2025-09-16T20:25:41.479Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "llama-cloud-services"
|
||||
version = "0.6.61"
|
||||
version = "0.6.68"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
@@ -1631,7 +1631,7 @@ dev = [
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.1.7,<9" },
|
||||
{ name = "eval-type-backport", marker = "python_full_version < '3.10'", specifier = ">=0.2.0,<0.3" },
|
||||
{ name = "llama-cloud", specifier = "==0.1.37" },
|
||||
{ name = "llama-cloud", specifier = "==0.1.42" },
|
||||
{ name = "llama-index-core", specifier = ">=0.12.0" },
|
||||
{ name = "packaging", specifier = ">=25.0" },
|
||||
{ name = "platformdirs", specifier = ">=4.3.7,<5" },
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
# LlamaReport (beta/invite-only)
|
||||
|
||||
LlamaReport is a prebuilt agentic report builder that can be used to build reports from a variety of data sources.
|
||||
|
||||
The python SDK for interacting with the LlamaReport API. The SDK provides two main classes:
|
||||
|
||||
- `LlamaReport`: For managing reports (create, list, delete)
|
||||
- `ReportClient`: For working with a specific report (editing, approving, etc.)
|
||||
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
pip install llama-cloud-services
|
||||
```
|
||||
|
||||
```python
|
||||
from llama_cloud_services import LlamaReport
|
||||
|
||||
# Initialize the client
|
||||
client = LlamaReport(
|
||||
api_key="your-api-key",
|
||||
# Optional: Specify project_id, organization_id, async_httpx_client
|
||||
)
|
||||
|
||||
# Create a new report
|
||||
report = client.create_report(
|
||||
"My Report",
|
||||
# must have one of template_text or template_instructions
|
||||
template_text="Your template text",
|
||||
template_instructions="Instructions for the template",
|
||||
# must have one of input_files or retriever_id
|
||||
input_files=["data1.pdf", "data2.pdf"],
|
||||
retriever_id="retriever-id",
|
||||
)
|
||||
```
|
||||
|
||||
## Working with Reports
|
||||
|
||||
The typical workflow for a report involves:
|
||||
|
||||
1. Creating the report
|
||||
2. Waiting for and approving the plan
|
||||
3. Waiting for report generation
|
||||
4. Making edits to the report
|
||||
|
||||
Here's a complete example:
|
||||
|
||||
```python
|
||||
# Create a report
|
||||
report = client.create_report(
|
||||
"Quarterly Analysis", input_files=["q1_data.pdf", "q2_data.pdf"]
|
||||
)
|
||||
|
||||
# Wait for the plan to be ready
|
||||
plan = report.wait_for_plan()
|
||||
|
||||
# Option 1: Directly approve the plan
|
||||
report.update_plan(action="approve")
|
||||
|
||||
# Option 2: Suggest and review edits to the plan
|
||||
suggestions = report.suggest_edits(
|
||||
"Can you add a section about market trends?"
|
||||
)
|
||||
for suggestion in suggestions:
|
||||
print(suggestion)
|
||||
|
||||
# Accept or reject the suggestion
|
||||
if input("Accept? (y/n): ").lower() == "y":
|
||||
report.accept_edit(suggestion)
|
||||
else:
|
||||
report.reject_edit(suggestion)
|
||||
|
||||
# Wait for the report to complete
|
||||
report = report.wait_for_completion()
|
||||
|
||||
# Make edits to the final report
|
||||
suggestions = report.suggest_edits("Make the executive summary more concise")
|
||||
|
||||
# Review and accept/reject suggestions as above
|
||||
...
|
||||
```
|
||||
|
||||
### Getting the Final Report
|
||||
|
||||
Once you are satisfied with the report, you can get the final report object and use the content as you see fit.
|
||||
|
||||
Here's an example of printing out the final report:
|
||||
|
||||
```python
|
||||
report = report.get()
|
||||
report_text = "\n\n".join([block.template for block in report.blocks])
|
||||
|
||||
print(report_text)
|
||||
```
|
||||
|
||||
## Additional Features
|
||||
|
||||
- **Async Support**: All methods have async counterparts: `create_report` -> `acreate_report`, `wait_for_plan` -> `await_for_plan`, etc.
|
||||
- **Automatic Chat History**: The SDK automatically keeps track of chat history for each suggestion, unless you specify `auto_history=False` in `suggest_edits`.
|
||||
- **Custom HTTP Client**: You can provide your own `httpx.AsyncClient` to the `LlamaReport` class.
|
||||
- **Project and Organization IDs**: You can specify `project_id` and `organization_id` to use a specific project or organization.
|
||||
+68
-22
@@ -8,10 +8,11 @@ import subprocess
|
||||
import sys
|
||||
import tomlkit
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
|
||||
def get_current_versions() -> tuple[str, str, str]:
|
||||
"""Get current versions from both pyproject.toml files."""
|
||||
def get_current_versions() -> tuple[str, str, str, str | None]:
|
||||
"""Get current versions from both pyproject.toml files and TS package.json."""
|
||||
# Read main pyproject.toml
|
||||
main_content = Path("py/pyproject.toml").read_text()
|
||||
main_doc = tomlkit.parse(main_content)
|
||||
@@ -34,11 +35,21 @@ def get_current_versions() -> tuple[str, str, str]:
|
||||
)
|
||||
break
|
||||
|
||||
return str(main_version), str(llama_parse_version), str(dependency_version)
|
||||
# Read TypeScript package.json version via helper
|
||||
ts_version: str = get_ts_version()
|
||||
|
||||
return (
|
||||
str(main_version),
|
||||
str(llama_parse_version),
|
||||
str(dependency_version),
|
||||
str(ts_version) if ts_version is not None else None,
|
||||
)
|
||||
|
||||
|
||||
def validate_versions(
|
||||
main_version: str, llama_parse_version: str, dependency_version: str
|
||||
main_version: str,
|
||||
llama_parse_version: str,
|
||||
dependency_version: str,
|
||||
) -> list[str]:
|
||||
"""Validate that versions are consistent and return warnings."""
|
||||
warnings = []
|
||||
@@ -60,7 +71,7 @@ def validate_versions(
|
||||
|
||||
|
||||
def set_version(version: str) -> None:
|
||||
"""Set version across all pyproject.toml files using tomlkit to preserve formatting."""
|
||||
"""Set version across Python projects (no TS change)."""
|
||||
# Update main pyproject.toml
|
||||
main_content = Path("py/pyproject.toml").read_text()
|
||||
main_doc = tomlkit.parse(main_content)
|
||||
@@ -79,7 +90,26 @@ def set_version(version: str) -> None:
|
||||
break
|
||||
Path("py/llama_parse/pyproject.toml").write_text(tomlkit.dumps(llama_parse_doc))
|
||||
|
||||
click.echo(f"Updated all versions to {version}")
|
||||
click.echo(f"Updated Python versions to {version}")
|
||||
|
||||
|
||||
def get_ts_version() -> str:
|
||||
"""Read TypeScript package.json version (if present)."""
|
||||
ts_package_path = Path("ts/llama_cloud_services/package.json")
|
||||
package_data = json.loads(ts_package_path.read_text())
|
||||
data = package_data.get("version")
|
||||
if data is None:
|
||||
raise RuntimeError("TypeScript package.json version not found")
|
||||
return data
|
||||
|
||||
|
||||
def set_ts_version(version: str) -> None:
|
||||
"""Set TypeScript package.json version only."""
|
||||
ts_package_path = Path("ts/llama_cloud_services/package.json")
|
||||
package_data = json.loads(ts_package_path.read_text())
|
||||
package_data["version"] = version
|
||||
ts_package_path.write_text(json.dumps(package_data, indent=2) + "\n")
|
||||
click.echo(f"Updated TypeScript package.json version to {version}")
|
||||
|
||||
|
||||
def get_current_branch() -> str:
|
||||
@@ -90,7 +120,7 @@ def get_current_branch() -> str:
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def create_if_not_exists(version: str) -> None:
|
||||
def create_if_not_exists(version: str) -> str:
|
||||
"""Create a git tag and push it."""
|
||||
current_branch = get_current_branch()
|
||||
if current_branch != "main":
|
||||
@@ -99,27 +129,26 @@ def create_if_not_exists(version: str) -> None:
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
tag_name = f"v{version}"
|
||||
if not tag_exists(version):
|
||||
tag_name = f"v{version}" if version[0].isdigit() else version
|
||||
if not tag_exists(tag_name):
|
||||
# Create tag
|
||||
subprocess.run(["git", "tag", tag_name], check=True)
|
||||
click.echo(f"Created tag {tag_name}")
|
||||
else:
|
||||
click.echo(f"Tag {tag_name} already exists")
|
||||
return tag_name
|
||||
|
||||
|
||||
def tag_exists(version: str) -> bool:
|
||||
def tag_exists(tag_name: str) -> bool:
|
||||
"""Check if a git tag exists."""
|
||||
tag_name = f"v{version}"
|
||||
result = subprocess.run(
|
||||
["git", "tag", "-l", tag_name], capture_output=True, text=True, check=True
|
||||
)
|
||||
return tag_name in result.stdout.strip()
|
||||
|
||||
|
||||
def push_tag(version: str) -> None:
|
||||
def push_tag(tag_name: str) -> None:
|
||||
"""Push a git tag."""
|
||||
tag_name = f"v{version}"
|
||||
subprocess.run(["git", "push", "origin", tag_name], check=True)
|
||||
click.echo(f"Pushed tag {tag_name}")
|
||||
|
||||
@@ -133,12 +162,18 @@ def cli() -> None:
|
||||
@cli.command()
|
||||
def get() -> None:
|
||||
"""Get current versions and show validation warnings."""
|
||||
main_version, llama_parse_version, dependency_version = get_current_versions()
|
||||
(
|
||||
main_version,
|
||||
llama_parse_version,
|
||||
dependency_version,
|
||||
ts_version,
|
||||
) = get_current_versions()
|
||||
|
||||
click.echo("Current versions:")
|
||||
click.echo(f" llama-cloud-services: {main_version}")
|
||||
click.echo(f" llama-parse: {llama_parse_version}")
|
||||
click.echo(f" dependency reference: {dependency_version}")
|
||||
click.echo(f" typescript package: {ts_version}")
|
||||
|
||||
warnings = validate_versions(main_version, llama_parse_version, dependency_version)
|
||||
if warnings:
|
||||
@@ -151,9 +186,15 @@ def get() -> None:
|
||||
|
||||
@cli.command()
|
||||
@click.argument("version")
|
||||
def set(version: str) -> None:
|
||||
"""Set version across all pyproject.toml files."""
|
||||
set_version(version)
|
||||
@click.option("--js", is_flag=True, help="Update TypeScript package.json only")
|
||||
def set(version: str, js: bool) -> None:
|
||||
"""Set version for Python, TypeScript, or both (default: Python only)."""
|
||||
|
||||
if js:
|
||||
set_ts_version(version)
|
||||
return
|
||||
else:
|
||||
set_version(version)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@@ -165,15 +206,20 @@ def set(version: str) -> None:
|
||||
is_flag=True,
|
||||
help="Push the tag to the remote repository",
|
||||
)
|
||||
def tag(version: str | None = None, push: bool = False) -> None:
|
||||
@click.option(
|
||||
"--js",
|
||||
is_flag=True,
|
||||
help="tag TypeScript package.json only",
|
||||
)
|
||||
def tag(version: str | None = None, push: bool = False, js: bool = False) -> None:
|
||||
"""Create and push a git tag for the current version."""
|
||||
if not version:
|
||||
main_version, _, _ = get_current_versions()
|
||||
version = main_version
|
||||
main_version, _, _, js_version = get_current_versions()
|
||||
version = f"llama-cloud-services@{js_version}" if js else main_version
|
||||
|
||||
create_if_not_exists(version)
|
||||
tag_name = create_if_not_exists(version)
|
||||
if push:
|
||||
push_tag(version)
|
||||
push_tag(tag_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+6587
-3236
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "llama-cloud-services",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.6",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -9,6 +9,7 @@
|
||||
"dev": "bunchee --watch",
|
||||
"lint": "eslint src/ --ignore-pattern client/*.ts --no-warn-ignored",
|
||||
"format": "prettier --write ./src/",
|
||||
"format:check": "prettier --check ./src/",
|
||||
"test": "vitest run --testTimeout=60000",
|
||||
"test:watch": "vitest --watch",
|
||||
"test:ui": "vitest --ui",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createClient, createConfig } from "@hey-api/client-fetch";
|
||||
import { getEnv } from "@llamaindex/env";
|
||||
import { createClient } from "@hey-api/client-fetch";
|
||||
import { client as defaultClient } from "../../api";
|
||||
import {
|
||||
aggregateAgentDataApiV1BetaAgentDataAggregatePost,
|
||||
createAgentDataApiV1BetaAgentDataPost,
|
||||
@@ -24,38 +24,24 @@ import type {
|
||||
*/
|
||||
export class AgentClient<T = unknown> {
|
||||
private client: ReturnType<typeof createClient>;
|
||||
private baseUrl: string;
|
||||
private headers: Record<string, string>;
|
||||
private collection: string;
|
||||
private agentUrlId: string;
|
||||
private deploymentName: string;
|
||||
|
||||
constructor({
|
||||
apiKey = getEnv("LLAMA_CLOUD_API_KEY"),
|
||||
baseUrl = "https://api.cloud.llamaindex.ai/",
|
||||
client = defaultClient,
|
||||
collection = "default",
|
||||
agentUrlId = "_public",
|
||||
deploymentName = "_public",
|
||||
agentUrlId,
|
||||
}: {
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
client?: ReturnType<typeof createClient>;
|
||||
collection?: string;
|
||||
deploymentName?: string;
|
||||
// deprecated, use deploymentName instead
|
||||
agentUrlId?: string;
|
||||
}) {
|
||||
this.baseUrl = baseUrl;
|
||||
|
||||
this.headers = {
|
||||
"X-SDK-Name": "llamaindex-ts",
|
||||
...(apiKey && { Authorization: `Bearer ${apiKey}` }),
|
||||
};
|
||||
|
||||
this.client = createClient(
|
||||
createConfig({
|
||||
baseUrl: this.baseUrl,
|
||||
headers: this.headers,
|
||||
}),
|
||||
);
|
||||
|
||||
this.client = client;
|
||||
this.collection = collection;
|
||||
this.agentUrlId = agentUrlId;
|
||||
this.deploymentName = agentUrlId || deploymentName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +51,7 @@ export class AgentClient<T = unknown> {
|
||||
const response = await createAgentDataApiV1BetaAgentDataPost({
|
||||
throwOnError: true,
|
||||
body: {
|
||||
agent_slug: this.agentUrlId,
|
||||
deployment_name: this.deploymentName,
|
||||
collection: this.collection,
|
||||
data: data as Record<string, unknown>,
|
||||
},
|
||||
@@ -135,7 +121,7 @@ export class AgentClient<T = unknown> {
|
||||
const response = await searchAgentDataApiV1BetaAgentDataSearchPost({
|
||||
throwOnError: true,
|
||||
body: {
|
||||
agent_slug: this.agentUrlId,
|
||||
deployment_name: this.deploymentName,
|
||||
...(this.collection !== undefined && {
|
||||
collection: this.collection,
|
||||
}),
|
||||
@@ -182,7 +168,7 @@ export class AgentClient<T = unknown> {
|
||||
const response = await aggregateAgentDataApiV1BetaAgentDataAggregatePost({
|
||||
throwOnError: true,
|
||||
body: {
|
||||
agent_slug: this.agentUrlId,
|
||||
deployment_name: this.deploymentName,
|
||||
...(this.collection !== undefined && {
|
||||
collection: this.collection,
|
||||
}),
|
||||
@@ -226,7 +212,7 @@ export class AgentClient<T = unknown> {
|
||||
private transformResponse(data: AgentData): TypedAgentData<T> {
|
||||
const result: TypedAgentData<T> = {
|
||||
id: data.id!,
|
||||
agentUrlId: data.agent_slug,
|
||||
deploymentName: data.deployment_name,
|
||||
data: data.data as T,
|
||||
createdAt: new Date(data.created_at!),
|
||||
updatedAt: new Date(data.updated_at!),
|
||||
@@ -267,10 +253,10 @@ export interface AgentDataClientOptions {
|
||||
/** Base URL for the client */
|
||||
/** Base URL of the llama cloud api */
|
||||
baseUrl?: string;
|
||||
/** If running in an agent runtime, optionally provide the window url to infer the agent url id */
|
||||
/** If running in an agent runtime, optionally provide the window url to infer the deployment name */
|
||||
windowUrl?: string;
|
||||
/** Agent URL ID for the client, if not provided, it will be inferred from the window url, or fall back to "default" */
|
||||
agentUrlId?: string;
|
||||
/** Deployment name for the client, if not provided, it will be inferred from the window url, or fall back to "default" */
|
||||
deploymentName?: string;
|
||||
/** Collection name for the client, defaults to "default" */
|
||||
collection?: string;
|
||||
}
|
||||
@@ -281,27 +267,28 @@ export interface AgentDataClientOptions {
|
||||
* @returns A new AgentClient instance
|
||||
*/
|
||||
export function createAgentDataClient<T = unknown>({
|
||||
apiKey,
|
||||
baseUrl,
|
||||
client = defaultClient,
|
||||
windowUrl,
|
||||
env,
|
||||
deploymentName,
|
||||
agentUrlId,
|
||||
collection = "default",
|
||||
}: {
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
client?: ReturnType<typeof createClient>;
|
||||
windowUrl?: string;
|
||||
env?: Record<string, string>;
|
||||
deploymentName?: string;
|
||||
// deprecated, use deploymentName instead
|
||||
agentUrlId?: string;
|
||||
collection?: string;
|
||||
} = {}): AgentClient<T> {
|
||||
if (env && !agentUrlId) {
|
||||
agentUrlId =
|
||||
if (env && !deploymentName) {
|
||||
deploymentName =
|
||||
env.LLAMA_DEPLOY_DEPLOYMENT_NAME ||
|
||||
env.NEXT_PUBLIC_LLAMA_DEPLOY_DEPLOYMENT_NAME ||
|
||||
env.VITE_LLAMA_DEPLOY_DEPLOYMENT_NAME;
|
||||
}
|
||||
if (windowUrl && !agentUrlId) {
|
||||
if (windowUrl && !deploymentName) {
|
||||
try {
|
||||
const url = new URL(windowUrl);
|
||||
const path = url.pathname;
|
||||
@@ -310,20 +297,20 @@ export function createAgentDataClient<T = unknown>({
|
||||
url.hostname.includes("127.0.0.1");
|
||||
if (path.startsWith("/deployments/") && !isLocalhost) {
|
||||
// /deployments/<agent-url-id>/ui/ -> ["", "deployments", "<agent-url-id>", "ui"]
|
||||
agentUrlId = path.split("/")[2];
|
||||
deploymentName = path.split("/")[2];
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"Failed to infer agent url id from window url, falling back to default",
|
||||
"Failed to infer deployment name from window url, falling back to default",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new AgentClient({
|
||||
...(apiKey && { apiKey }),
|
||||
...(baseUrl && { baseUrl }),
|
||||
...(deploymentName && { deploymentName }),
|
||||
...(agentUrlId && { agentUrlId }),
|
||||
collection,
|
||||
client,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ export interface ExtractedData<T = unknown> {
|
||||
export interface TypedAgentData<T = unknown> {
|
||||
/** The unique ID of the agent data record. */
|
||||
id: string;
|
||||
/** The ID of the agent that created the data. */
|
||||
agentUrlId: string;
|
||||
/** The deployment name of the agent that created the data. */
|
||||
deploymentName: string;
|
||||
/** The collection of the agent data. */
|
||||
collection?: string;
|
||||
/** The data of the agent data. Usually an ExtractedData<SomeOtherType> */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -147,6 +147,10 @@ export class LlamaParseReader extends FileReader {
|
||||
output_s3_region?: string | undefined;
|
||||
preserve_layout_alignment_across_pages?: boolean | undefined;
|
||||
spreadsheet_extract_sub_tables?: boolean | undefined;
|
||||
specialized_chart_parsing_agentic?: boolean | undefined;
|
||||
specialized_chart_parsing_efficient?: boolean | undefined;
|
||||
specialized_chart_parsing_plus?: boolean | undefined;
|
||||
precise_bounding_box?: boolean | undefined;
|
||||
formatting_instruction?: string | undefined;
|
||||
parse_mode?: ParsingMode | undefined;
|
||||
system_prompt?: string | undefined;
|
||||
@@ -194,7 +198,7 @@ export class LlamaParseReader extends FileReader {
|
||||
? this.language
|
||||
: [this.language];
|
||||
this.stdout =
|
||||
params.stdout ?? typeof process !== "undefined"
|
||||
(params.stdout ?? typeof process !== "undefined")
|
||||
? process!.stdout
|
||||
: undefined;
|
||||
const apiKey = params.apiKey ?? getEnv("LLAMA_CLOUD_API_KEY");
|
||||
@@ -331,6 +335,11 @@ export class LlamaParseReader extends FileReader {
|
||||
preserve_layout_alignment_across_pages:
|
||||
this.preserve_layout_alignment_across_pages,
|
||||
spreadsheet_extract_sub_tables: this.spreadsheet_extract_sub_tables,
|
||||
specialized_chart_parsing_agentic: this.specialized_chart_parsing_agentic,
|
||||
specialized_chart_parsing_efficient:
|
||||
this.specialized_chart_parsing_efficient,
|
||||
specialized_chart_parsing_plus: this.specialized_chart_parsing_plus,
|
||||
precise_bounding_box: this.precise_bounding_box,
|
||||
formatting_instruction: this.formatting_instruction,
|
||||
parse_mode: this.parse_mode,
|
||||
system_prompt: this.system_prompt,
|
||||
|
||||
@@ -87,6 +87,10 @@ export const parseFormSchema = z.object({
|
||||
preserve_layout_alignment_across_pages: z.boolean().optional(),
|
||||
skip_diagonal_text: z.boolean().optional(),
|
||||
spreadsheet_extract_sub_tables: z.boolean().optional(),
|
||||
specialized_chart_parsing_agentic: z.boolean().optional(),
|
||||
specialized_chart_parsing_efficient: z.boolean().optional(),
|
||||
specialized_chart_parsing_plus: z.boolean().optional(),
|
||||
precise_bounding_box: z.boolean().optional(),
|
||||
structured_output: z.boolean().optional(),
|
||||
structured_output_json_schema: z.string().optional(),
|
||||
structured_output_json_schema_name: z.string().optional(),
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { listProjectsApiV1ProjectsGet, client } from "../src/api.js";
|
||||
|
||||
describe("Global client configuration", () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
it("adds X-SDK-Name header from global client config", async () => {
|
||||
const fetchSpy = vi
|
||||
.spyOn(globalThis, "fetch")
|
||||
.mockImplementation(async (input, init) => {
|
||||
// Validate the header is present on the outgoing request
|
||||
let headers: Headers;
|
||||
if (input && typeof input === "object" && "headers" in (input as any)) {
|
||||
headers = (input as Request).headers;
|
||||
} else {
|
||||
headers = new Headers((init && init.headers) || {});
|
||||
}
|
||||
expect(headers.get("X-SDK-Name")).toBe("llamaindex-ts");
|
||||
|
||||
return new Response(JSON.stringify([]), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger any request via the generated SDK (imported through src/api.ts)
|
||||
await listProjectsApiV1ProjectsGet({ throwOnError: false });
|
||||
|
||||
expect(fetchSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("respects additional custom headers set via setConfig", async () => {
|
||||
const prevConfig = client.getConfig();
|
||||
try {
|
||||
client.setConfig({
|
||||
...prevConfig,
|
||||
headers: {
|
||||
...(prevConfig.headers || {}),
|
||||
"X-Custom-Header": "custom-value",
|
||||
},
|
||||
});
|
||||
|
||||
const fetchSpy = vi
|
||||
.spyOn(globalThis, "fetch")
|
||||
.mockImplementation(async (input, init) => {
|
||||
let headers: Headers;
|
||||
if (
|
||||
input &&
|
||||
typeof input === "object" &&
|
||||
"headers" in (input as any)
|
||||
) {
|
||||
headers = (input as Request).headers;
|
||||
} else {
|
||||
headers = new Headers((init && init.headers) || {});
|
||||
}
|
||||
expect(headers.get("X-SDK-Name")).toBe("llamaindex-ts");
|
||||
expect(headers.get("X-Custom-Header")).toBe("custom-value");
|
||||
|
||||
return new Response(JSON.stringify([]), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
});
|
||||
|
||||
await listProjectsApiV1ProjectsGet({ throwOnError: false });
|
||||
expect(fetchSpy).toHaveBeenCalledOnce();
|
||||
} finally {
|
||||
// Restore original configuration to avoid test cross-talk
|
||||
client.setConfig(prevConfig);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user