mirror of
https://github.com/run-llama/llama_cloud_services.git
synced 2026-07-20 00:54:09 -04:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e297ba30a8 | |||
| fb3c843ab6 | |||
| 24bdf748e1 | |||
| 79d0c89484 | |||
| 6c59617fbb | |||
| 5eb8c68202 | |||
| a67090a786 | |||
| e30e0170da | |||
| b17f882849 | |||
| abea631210 | |||
| 05ba8b6310 |
+417
-558
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,295 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Using llama-parse with AstraDB"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this notebook, we show a basic RAG-style example that uses `llama-parse` to parse a PDF document, store the corresponding document into a vector store (`AstraDB`) and finally, perform some basic queries against that store. The notebook is modeled after the quick start notebooks and hence is meant as a way of getting started with `llama-parse`, backed by a vector database."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Requirements"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# First, install the required dependencies\n",
|
||||
"%pip install --quiet llama-index llama-parse llama-index-vector-stores-astra-db llama-index-llms-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Configuration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import openai\n",
|
||||
"\n",
|
||||
"from getpass import getpass\n",
|
||||
"\n",
|
||||
"# Get all required API keys and parameters\n",
|
||||
"llama_cloud_api_key = getpass(\"Enter your Llama Index Cloud API Key: \")\n",
|
||||
"api_endpoint = input(\"Enter your Astra DB API Endpoint: \")\n",
|
||||
"token = getpass(\"Enter your Astra DB Token: \")\n",
|
||||
"namespace = (\n",
|
||||
" input(\"Enter your Astra DB namespace (optional, must exist on Astra): \") or None\n",
|
||||
")\n",
|
||||
"openai_api_key = getpass(\"Enter your OpenAI API Key: \")\n",
|
||||
"\n",
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = llama_cloud_api_key\n",
|
||||
"openai.api_key = openai_api_key"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the sync code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Using llama-parse to parse a PDF"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Download complete.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Grab a PDF from Arxiv for indexing\n",
|
||||
"import requests\n",
|
||||
"\n",
|
||||
"# The URL of the file you want to download\n",
|
||||
"url = \"https://arxiv.org/pdf/1706.03762.pdf\"\n",
|
||||
"# The local path where you want to save the file\n",
|
||||
"file_path = \"./attention.pdf\"\n",
|
||||
"\n",
|
||||
"# Perform the HTTP request\n",
|
||||
"response = requests.get(url)\n",
|
||||
"\n",
|
||||
"# Check if the request was successful\n",
|
||||
"if response.status_code == 200:\n",
|
||||
" # Open the file in binary write mode and save the content\n",
|
||||
" with open(file_path, \"wb\") as file:\n",
|
||||
" file.write(response.content)\n",
|
||||
" print(\"Download complete.\")\n",
|
||||
"else:\n",
|
||||
" print(\"Error downloading the file.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id ce3909a7-54cf-438b-849a-fe9a903b0c71\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"documents = LlamaParse(result_type=\"text\").load_data(file_path)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'rmer - model architecture.\\nThe Transformer follows this overall architecture using stacked self-attention and point-wise, fully\\nconnected layers for both the encoder and decoder, shown in the left and right halves of Figure 1,\\nrespectively.\\n3.1 Encoder and Decoder Stacks\\nEncoder: The encoder is composed of a stack of N = 6 identical layers. Each layer has two\\nsub-layers. The first is a multi-head self-attention mechanism, and the second is a simple, position-\\nwise fully connected feed-forward network. We employ a residual connection [11] around each of\\nthe two sub-layers, followed by layer normalization [1]. That is, the output of each sub-layer is\\nLayerNorm(x + Sublayer(x)), where Sublayer(x) is the function implemented by the sub-layer\\nitself. To facilitate these residual connections, all sub-layers in the model, as well as the embedding\\nlayers, produce outputs of dimension dmodel = 512.\\nDecoder: The decoder is also composed of a stack of N = 6 identical layers. In addition '"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Take a quick look at some of the parsed text from the document:\n",
|
||||
"documents[0].get_content()[10000:11000]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Storing into Astra DB"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.vector_stores.astra_db import AstraDBVectorStore\n",
|
||||
"\n",
|
||||
"astra_db_store = AstraDBVectorStore(\n",
|
||||
" token=token,\n",
|
||||
" api_endpoint=api_endpoint,\n",
|
||||
" namespace=namespace,\n",
|
||||
" collection_name=\"astra_v_table_llamaparse\",\n",
|
||||
" embedding_dimension=1536,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.node_parser import SimpleNodeParser\n",
|
||||
"\n",
|
||||
"node_parser = SimpleNodeParser()\n",
|
||||
"\n",
|
||||
"nodes = node_parser.get_nodes_from_documents(documents)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.embeddings.openai import OpenAIEmbedding\n",
|
||||
"from llama_index.core import VectorStoreIndex, StorageContext\n",
|
||||
"\n",
|
||||
"storage_context = StorageContext.from_defaults(vector_store=astra_db_store)\n",
|
||||
"\n",
|
||||
"index = VectorStoreIndex(\n",
|
||||
" nodes=nodes,\n",
|
||||
" storage_context=storage_context,\n",
|
||||
" embed_model=OpenAIEmbedding(api_key=openai_api_key),\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Simple RAG Example"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"query_engine = index.as_query_engine(similarity_top_k=15)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"***********New LlamaParse+ Basic Query Engine***********\n",
|
||||
"Multi-Head Attention is also known as multi-headed self-attention.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"query = \"What is Multi-Head Attention also known as?\"\n",
|
||||
"\n",
|
||||
"response_1 = query_engine.query(query)\n",
|
||||
"print(\"\\n***********New LlamaParse+ Basic Query Engine***********\")\n",
|
||||
"print(response_1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'We used beam search as described in the previous section, but no\\ncheckpoint averaging. We present these results in Table 3.\\nIn Table 3 rows (A), we vary the number of attention heads and the attention key and value dimensions,\\nkeeping the amount of computation constant, as described in Section 3.2.2. While single-head\\nattention is 0.9 BLEU worse than the best setting, quality also drops off with too many heads.\\nIn Table 3 rows (B), we observe that reducing the attention key size dk hurts model quality. This\\nsuggests that determining compatibility is not easy and that a more sophisticated compatibility\\nfunction than dot product may be beneficial. We further observe in rows (C) and (D) that, as expected,\\nbigger models are better, and dropout is very helpful in avoiding over-fitting. In row (E) we replace our\\nsinusoidal positional encoding with learned positional embeddings [9], and observe nearly identical\\nresults to the base model.\\n6.3 English Constituency Parsing\\nTo evaluate if the Transformer can generalize to other tasks we performed experiments on English\\nconstituency parsing. This task presents specific challenges: the output is subject to strong structural\\nconstraints and is significantly longer than the input. Furthermore, RNN sequence-to-sequence\\nmodels have not been able to attain state-of-the-art results in small-data regimes [37].\\nWe trained a 4-layer transformer with dmodel = 1024 on the Wall Street Journal (WSJ) portion of the\\nPenn Treebank [25], about 40K training sentences. We also trained it in a semi-supervised setting,\\nusing the larger high-confidence and BerkleyParser corpora from with approximately 17M sentences\\n[37]. We used a vocabulary of 16K tokens for the WSJ only setting and a vocabulary of 32K tokens\\nfor the semi-supervised setting.\\nWe performed only a small number of experiments to select the dropout, both attention and residual\\n(section 5.4), learning rates and beam size on the Section 22 development set, all other parameters\\nremained unchanged from the English-to-German base translation model. During inference, we\\n 9\\n---\\nTable 4: The Transformer generalizes well to English constituency parsing (Results are on Section 23\\nof WSJ)\\n Parser Training WSJ 23 F1\\n Vinyals & Kaiser el al. (2014) [37] WSJ only, discriminative 88.3\\n Petrov et al. (2006) [29] WSJ only, discriminative 90.4\\n Zhu et al. (2013) [40] WSJ only, discriminative 90.4\\n Dyer et al. (2016) [8] WSJ only, discriminative 91.7\\n Transformer (4 layers) WSJ only, discriminative 91.3\\n Zhu et al. (2013) [40] semi-supervised 91.3\\n Huang & Harper (2009) [14] semi-supervised 91.3\\n McClosky et al. (2006) [26] semi-supervised 92.1\\n Vinyals & Kaiser el al. (2014) [37] semi-supervised 92.1\\n Transformer (4 layers) semi-supervised 92.7\\n Luong et al. (2015) [23] multi-task 93.0\\n Dyer et al. (2016) [8] generative 93.3\\nincreased the maximum output length to input length + 300. We used a beam size of 21 and α = 0.3\\nfor both WSJ only and the semi-supervised setting.\\nOur results in Table 4 show that despite the lack of task-specific tuning our model performs sur-\\nprisingly well, yielding better results than all previously reported models with the exception of the\\nRecurrent Neural Network Grammar [8].\\nIn contrast to RNN sequence-to-sequence models [37], the Transformer outperforms the Berkeley-\\nParser [29] even when training only on the WSJ training set of 40K sentences.\\n7 Conclusion\\nIn this work, we presented the Transformer, the first sequence transduction model based entirely on\\nattention, replacing the recurrent layers most commonly used in encoder-decoder architectures with\\nmulti-headed self-attention.\\nFor translation tasks, the Transformer can be trained significantly faster than architectures based\\non recurrent or convolutional layers.'"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Take a look at one of the source nodes from the response\n",
|
||||
"response_1.source_nodes[0].get_content()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
@@ -13,32 +13,14 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install llama-index llama-parse"
|
||||
"%pip install llama-index llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--2024-02-02 11:10:10-- https://arxiv.org/pdf/1706.03762.pdf\n",
|
||||
"Resolving arxiv.org (arxiv.org)... 151.101.131.42, 151.101.3.42, 151.101.67.42, ...\n",
|
||||
"Connecting to arxiv.org (arxiv.org)|151.101.131.42|:443... connected.\n",
|
||||
"HTTP request sent, awaiting response... 200 OK\n",
|
||||
"Length: 2215244 (2.1M) [application/pdf]\n",
|
||||
"Saving to: ‘./attention.pdf’\n",
|
||||
"\n",
|
||||
"./attention.pdf 100%[===================>] 2.11M --.-KB/s in 0.08s \n",
|
||||
"\n",
|
||||
"2024-02-02 11:10:10 (25.9 MB/s) - ‘./attention.pdf’ saved [2215244/2215244]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget \"https://arxiv.org/pdf/1706.03762.pdf\" -O \"./attention.pdf\""
|
||||
]
|
||||
@@ -49,11 +31,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the sync code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-...\""
|
||||
@@ -68,14 +45,14 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id dd0b8e31-0c09-4497-b78a-cc1c92f1d6cf\n"
|
||||
"Started parsing the file under job_id 79ae653c-4598-4bd0-ba6e-b3dab7eab57e\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"documents = LlamaParse(result_type=\"text\").load_data(\"./attention.pdf\")"
|
||||
"result = await LlamaParse().aparse(\"./attention.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -87,23 +64,62 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ad\n",
|
||||
"1 Introduction\n",
|
||||
"Recurrent neural networks, long short-term memory [13] and gated recurrent [7] neural networks\n",
|
||||
"in particular, have been firmly established as state of the art approaches in sequence modeling and\n",
|
||||
"transduction problems such as language modeling and machine translation [35, 2, 5]. Numerous\n",
|
||||
"efforts have since continued to push the boundaries of recurrent language models and encoder-decoder\n",
|
||||
"architectures [38, 24, 15].\n",
|
||||
"Recurrent models typically factor computation along the symbol positions of the input and output\n",
|
||||
"sequences. Aligning the positions to steps in computation time, they generate a sequence of hidden\n",
|
||||
"states ht, as a function of the previous hidden state ht−1 and the input for position t. This inherently\n",
|
||||
"sequential nature precludes parallelization within training examples, which becomes critical at longer\n",
|
||||
"sequence lengths, as memory constraints limit batching across examples. Recent work has achieved\n",
|
||||
"significant improvements in computational efficiency through factorization tricks [21] and conditional\n",
|
||||
"computation [32], while also improving model performance in case of the latter. The fundamental\n",
|
||||
"constraint of sequential computation, however, remains.\n",
|
||||
"Attention mechanisms have become an integral part of compelling sequence modeling and transduc-\n",
|
||||
"tion models in various tasks, allowing modeling of dependencies without regard to their distance in\n",
|
||||
"the input or output sequences [2, 19]. In all but a few cases [27], however, such attention mechanisms\n",
|
||||
"are used in conjunction with a recurrent network.\n",
|
||||
"In this work we propose the Transformer, a model architecture eschewing recurrence and instead\n",
|
||||
"relying entirely on an attention mechanism to draw global dependencies between input and output.\n",
|
||||
"The Transformer allows for significantly more parallelization and can reach a new state of the art in\n",
|
||||
"translation quality after being trained for as little as twelve hours on eight P100 GPUs.\n",
|
||||
"2 Background\n",
|
||||
"2 Background\n",
|
||||
"The goal of reducing sequential computation also forms the foundation of the Extended Neural GPU\n",
|
||||
"[16], ByteNet [18] and ConvS2S [9], all of which use convolutional neural networks as basic building\n",
|
||||
"block, computing hidden representations in parallel for all input and output positions. In these models,\n",
|
||||
"the number of operations required to relate signals from two arbitrary input or output positions grows\n",
|
||||
"in the distance between positions, linearly for ConvS2S and logarithmically for ByteNet. This makes\n",
|
||||
"it more difficult to learn dependencies between distant positions [12]. In the Transformer this is\n",
|
||||
"reduced to a constant number of operations, albeit at the cost of reduced effective res\n"
|
||||
"reduced to a constant number of operations, albeit at the cost of reduced effective resolution due\n",
|
||||
"to averaging attention-weighted positions, an effect we counteract with Multi-Head Attention as\n",
|
||||
"described in section 3.2.\n",
|
||||
"Self-attention, sometimes called intra-attention is an attention mechanism relating different positions\n",
|
||||
"of a single sequence in order to compute a representation of the sequence. Self-attention has been\n",
|
||||
"used successfully in a variety of tasks including reading comprehension, abstractive summarization,\n",
|
||||
"textual entailment and learning task-independent sentence representations [4, 27, 28, 22].\n",
|
||||
"End-to-end memory networks are based on a recurrent attention mechanism instead of sequence-\n",
|
||||
"aligned recurrence and have been shown to perform well on simple-language question answering and\n",
|
||||
"language modeling tasks [34].\n",
|
||||
"To the best of our knowledge, however, the Transformer is the first transduction model relying\n",
|
||||
"entirely on self-attention to compute representations of its input and output without using sequence-\n",
|
||||
"aligned RNNs or convolution. In the following sections, we will describe the Transformer, motivate\n",
|
||||
"self-attention and discuss its advantages over models such as [17, 18] and [9].\n",
|
||||
"3 Model Architecture\n",
|
||||
"Most competitive neural sequence transduction models have an encoder-decoder structure [5, 2, 35].\n",
|
||||
"Here, the encoder maps an input sequence of symbol representations (x1, ..., xn) to a sequence\n",
|
||||
"of continuous representations z = (z1, ..., zn). Given z, the decoder then generates an output\n",
|
||||
"sequence (y1, ..., ym) of symbols one element at a time. At each step the model is auto-regressive\n",
|
||||
"[10], consuming the previously generated symbols as additional input when generating the next.\n",
|
||||
" 2\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(documents[0].text[6000:7000])"
|
||||
"documents = result.get_text_documents(split_by_page=True)\n",
|
||||
"print(documents[1].text)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -115,48 +131,45 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id d4531453-1bbb-48c4-8324-ae9fea9f2fa2\n"
|
||||
"arXiv:1706.03762v7 [cs.CL] 2 Aug 2023\n",
|
||||
"\n",
|
||||
"Provided proper attribution is provided, Google hereby grants permission to reproduce the tables and figures in this paper solely for use in journalistic or scholarly works.\n",
|
||||
"\n",
|
||||
"# Attention Is All You Need\n",
|
||||
"\n",
|
||||
"Ashish Vaswani∗ Noam Shazeer∗ Niki Parmar∗ Jakob Uszkoreit∗\n",
|
||||
"\n",
|
||||
"Google Brain Google Brain Google Research Google Research\n",
|
||||
"\n",
|
||||
"avaswani@google.com noam@google.com nikip@google.com usz@google.com\n",
|
||||
"\n",
|
||||
"Llion Jones∗ Aidan N. Gomez∗ † Łukasz Kaiser∗\n",
|
||||
"\n",
|
||||
"Google Research University of Toronto Google Brain\n",
|
||||
"\n",
|
||||
"llion@google.com aidan@cs.toronto.edu lukaszkaiser@google.com\n",
|
||||
"\n",
|
||||
"Illia Polosukhin∗ ‡\n",
|
||||
"\n",
|
||||
"illia.polosukhin@gmail.com\n",
|
||||
"\n",
|
||||
"# Abstract\n",
|
||||
"\n",
|
||||
"The dominant sequence transduction models are based on complex recurrent or convolutional neural networks that include an encoder and a decoder. The best performing models also connect the encoder and decoder through an attention mechanism. We propose a new simple network architecture, the Transformer, based solely on attention mechanisms, dispensing with recurrence and convolutions entirely. Experiments on two machine translation tasks show these models to be superior in quality while being more parallelizable and requiring significantly less time to train. Our model achieves 28.4 BLEU on the WMT 2014 English-to-German translation task, improving over the existing best results, including ensembles, by over 2 BLEU. On the WMT 2014 English-to-French translation task, our model establishes a new single-model state-of-the-art BLEU score of 41.8 after training for 3.5 days on eight GPUs, a small fraction of the training costs of the best models from the literature. We show that the Transformer generalizes well to other tasks by applying it successfully to English constituency parsing both with large and limited training data.\n",
|
||||
"\n",
|
||||
"∗Equal contribution. Listing order is random. Jakob proposed replacing RNNs with self-attention and started the effort to evaluate this idea. Ashish, with Illia, designed and implemented the first Transformer models and has been crucially involved in every aspect of this work. Noam proposed scaled dot-product attention, multi-head attention and the parameter-free position representation and became the other person involved in nearly every detail. Niki designed, implemented, tuned and evaluated countless model variants in our original codebase and tensor2tensor. Llion also experimented with novel model variants, was responsible for our initial codebase, and efficient inference and visualizations. Lukasz and Aidan spent countless long days designing various parts of and implementing tensor2tensor, replacing our earlier codebase, greatly improving results and massively accelerating our research.\n",
|
||||
"\n",
|
||||
"†Work performed while at Google Brain.\n",
|
||||
"\n",
|
||||
"‡Work performed while at Google Research.\n",
|
||||
"\n",
|
||||
"31st Conference on Neural Information Processing Systems (NIPS 2017), Long Beach, CA, USA.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"documents = LlamaParse(result_type=\"markdown\").load_data(\"./attention.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ction describes the training regime for our models.\n",
|
||||
"\n",
|
||||
"##### Training Data and Batching\n",
|
||||
"\n",
|
||||
"We trained on the standard WMT 2014 English-German dataset consisting of about 4.5 million\n",
|
||||
"sentence pairs. Sentences were encoded using byte-pair encoding [3], which has a shared source-\n",
|
||||
"target vocabulary of about 37000 tokens. For English-French, we used the significantly larger WMT\n",
|
||||
"2014 English-French dataset consisting of 36M sentences and split tokens into a 32000 word-piece\n",
|
||||
"vocabulary [38]. Sentence pairs were batched together by approximate sequence length. Each training\n",
|
||||
"batch contained a set of sentence pairs containing approximately 25000 source tokens and 25000\n",
|
||||
"target tokens.\n",
|
||||
"\n",
|
||||
"##### Hardware and Schedule\n",
|
||||
"\n",
|
||||
"We trained our models on one machine with 8 NVIDIA P100 GPUs. For our base models using\n",
|
||||
"the hyperparameters described throughout the paper, each training step took about 0.4 seconds. We\n",
|
||||
"trained the base models for a total of 100,000 steps or 12 hours. For our big models,(described on the\n",
|
||||
"bo...\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(documents[0].text[20000:21000] + \"...\")"
|
||||
"documents = result.get_markdown_documents(split_by_page=True)\n",
|
||||
"print(documents[0].text)"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -31,11 +31,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install llama-index\n",
|
||||
"!pip install llama-index-core\n",
|
||||
"!pip install llama-index-llms-anthropic llama-index-multi-modal-llms-anthropic\n",
|
||||
"!pip install llama-index-embeddings-huggingface\n",
|
||||
"!pip install llama-cloud-services"
|
||||
"%pip install llama-index\n",
|
||||
"%pip install llama-index-core\n",
|
||||
"%pip install llama-index-llms-anthropic\n",
|
||||
"%pip install llama-index-embeddings-huggingface\n",
|
||||
"%pip install llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -45,11 +45,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the async code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# API access to llama-cloud\n",
|
||||
@@ -68,7 +63,7 @@
|
||||
"source": [
|
||||
"from llama_index.llms.anthropic import Anthropic\n",
|
||||
"\n",
|
||||
"llm = Anthropic(model=\"claude-3-opus-20240229\", temperature=0.0)"
|
||||
"llm = Anthropic(model=\"claude-3-5-sonnet-20241022\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -131,28 +126,8 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(verbose=True)\n",
|
||||
"json_objs = parser.get_json_result(\"./uber_10q_march_2022.pdf\")\n",
|
||||
"json_list = json_objs[0][\"pages\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b26d21d1-05b5-4f49-b937-c13106a84015",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.schema import TextNode\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_text_nodes(json_list: List[dict]):\n",
|
||||
" text_nodes = []\n",
|
||||
" for idx, page in enumerate(json_list):\n",
|
||||
" text_node = TextNode(text=page[\"text\"], metadata={\"page\": page[\"page\"]})\n",
|
||||
" text_nodes.append(text_node)\n",
|
||||
" return text_nodes"
|
||||
"parser = LlamaParse(take_screenshot=True)\n",
|
||||
"result = await parser.aparse(\"./uber_10q_march_2022.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -162,7 +137,12 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"text_nodes = get_text_nodes(json_list)"
|
||||
"text_nodes = await result.aget_text_nodes(split_by_page=True)\n",
|
||||
"image_nodes = await result.aget_image_nodes(\n",
|
||||
" include_screenshot_images=True,\n",
|
||||
" include_object_images=True,\n",
|
||||
" image_download_dir=\"./uber_10q_images\",\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -172,7 +152,7 @@
|
||||
"source": [
|
||||
"## Extract/Index images from image dicts\n",
|
||||
"\n",
|
||||
"Here we use a multimodal model to extract and index images from image dictionaries."
|
||||
"Here we use a multimodal model to caption images and create text nodes for indexing."
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -190,27 +170,32 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# call get_images on parser, convert to ImageDocuments\n",
|
||||
"!mkdir llama2_images\n",
|
||||
"!mkdir -p llama2_images\n",
|
||||
"\n",
|
||||
"from llama_index.core.schema import ImageDocument\n",
|
||||
"from llama_index.multi_modal_llms.anthropic import AnthropicMultiModal\n",
|
||||
"from llama_index.core.llms import ChatMessage, ImageBlock, TextBlock\n",
|
||||
"from llama_index.core.schema import ImageNode, TextNode\n",
|
||||
"from llama_index.llms.anthropic import Anthropic\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_image_text_nodes(json_objs: List[dict]):\n",
|
||||
"def get_image_text_nodes(image_nodes: list[ImageNode]):\n",
|
||||
" \"\"\"Extract out text from images using a multimodal model.\"\"\"\n",
|
||||
" anthropic_mm_llm = AnthropicMultiModal(max_tokens=300)\n",
|
||||
" image_dicts = parser.get_images(json_objs, download_path=\"llama2_images\")\n",
|
||||
" image_documents = []\n",
|
||||
" llm = Anthropic(model=\"claude-3-5-haiku-20241022\", max_tokens=300)\n",
|
||||
" img_text_nodes = []\n",
|
||||
" for image_dict in image_dicts:\n",
|
||||
" image_doc = ImageDocument(image_path=image_dict[\"path\"])\n",
|
||||
" response = anthropic_mm_llm.complete(\n",
|
||||
" prompt=\"Describe the images as alt text\",\n",
|
||||
" image_documents=[image_doc],\n",
|
||||
" for image_node in image_nodes:\n",
|
||||
" image_path = image_node.image_path\n",
|
||||
" message = ChatMessage(\n",
|
||||
" role=\"user\",\n",
|
||||
" blocks=[\n",
|
||||
" TextBlock(text=\"Describe the images as alt text\"),\n",
|
||||
" ImageBlock(path=image_path),\n",
|
||||
" ],\n",
|
||||
" )\n",
|
||||
" response = llm.chat([message])\n",
|
||||
" text_node = TextNode(\n",
|
||||
" text=str(response.message.content), metadata={\"path\": image_path}\n",
|
||||
" )\n",
|
||||
" text_node = TextNode(text=str(response), metadata={\"path\": image_dict[\"path\"]})\n",
|
||||
" img_text_nodes.append(text_node)\n",
|
||||
"\n",
|
||||
" return img_text_nodes"
|
||||
]
|
||||
},
|
||||
@@ -221,7 +206,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"image_text_nodes = get_image_text_nodes(json_objs)"
|
||||
"image_text_nodes = get_image_text_nodes(image_nodes)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
+209
-659
File diff suppressed because it is too large
Load Diff
@@ -31,14 +31,9 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the sync code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-...\""
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-...\""
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -79,8 +74,9 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(result_type=\"text\", language=\"fr\")\n",
|
||||
"documents = parser.load_data(\"./treasury_report.pdf\")"
|
||||
"parser = LlamaParse(language=\"fr\")\n",
|
||||
"result = await parser.aparse(\"./treasury_report.pdf\")\n",
|
||||
"documents = result.get_text_documents(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -252,8 +248,9 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(result_type=\"text\", language=\"ch_sim\")\n",
|
||||
"documents = parser.load_data(\"./chinese_pdf.pdf\")"
|
||||
"parser = LlamaParse(language=\"ch_sim\")\n",
|
||||
"result = await parser.aparse(\"./chinese_pdf.pdf\")\n",
|
||||
"documents = result.get_text_documents(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -406,8 +403,9 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"base_parser = LlamaParse(result_type=\"text\", language=\"en\")\n",
|
||||
"base_documents = parser.load_data(\"./chinese_pdf2.pdf\")"
|
||||
"base_parser = LlamaParse(language=\"en\")\n",
|
||||
"result = await base_parser.aparse(\"./chinese_pdf2.pdf\")\n",
|
||||
"base_documents = result.get_text_documents(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -60,11 +60,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the sync code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import requests\n",
|
||||
"import pymongo\n",
|
||||
"\n",
|
||||
@@ -72,7 +67,7 @@
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"from llama_index.embeddings.openai import OpenAIEmbedding\n",
|
||||
"from llama_index.core import VectorStoreIndex, StorageContext\n",
|
||||
"from llama_index.core.node_parser import SimpleNodeParser"
|
||||
"from llama_index.core.node_parser import SentenceSplitter"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -137,7 +132,8 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"documents = LlamaParse(result_type=\"text\").load_data(file_path)"
|
||||
"result = await LlamaParse().aparse(file_path)\n",
|
||||
"documents = result.get_text_documents(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -203,7 +199,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"node_parser = SimpleNodeParser()\n",
|
||||
"node_parser = SentenceSplitter()\n",
|
||||
"\n",
|
||||
"nodes = node_parser.get_nodes_from_documents(documents)"
|
||||
]
|
||||
|
||||
@@ -1,544 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# LlamaParse - Parsing comic books with parsing intructions\n",
|
||||
"Parsing intructions allow you to instruct our parsing model the same way you would instruct an LLM!\n",
|
||||
"\n",
|
||||
"They can be useful to help the parser get better results on complex document layouts, to extract data in a specific format, or to transform the document in other ways.\n",
|
||||
"\n",
|
||||
"Using Parsing Instruction you will get better results out of LlamaParse on complicated documents, and also be able to simplify your application code."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Installation\n",
|
||||
"\n",
|
||||
"Parsing instructions are part of the llamaParse API. They can be accessed by directly specifying the parsing_instruction parameter in the API or by using the LlamaParse python module (which we will use for this tutorial).\n",
|
||||
"\n",
|
||||
"To install llama-parse, just get it from PIP:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Collecting llama-parse\n",
|
||||
" Downloading llama_parse-0.3.8-py3-none-any.whl (6.7 kB)\n",
|
||||
"Collecting llama-index-core>=0.10.7 (from llama-parse)\n",
|
||||
" Downloading llama_index_core-0.10.19-py3-none-any.whl (15.3 MB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m15.3/15.3 MB\u001b[0m \u001b[31m31.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: PyYAML>=6.0.1 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (6.0.1)\n",
|
||||
"Requirement already satisfied: SQLAlchemy[asyncio]>=1.4.49 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (2.0.28)\n",
|
||||
"Requirement already satisfied: aiohttp<4.0.0,>=3.8.6 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (3.9.3)\n",
|
||||
"Collecting dataclasses-json (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading dataclasses_json-0.6.4-py3-none-any.whl (28 kB)\n",
|
||||
"Collecting deprecated>=1.2.9.3 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading Deprecated-1.2.14-py2.py3-none-any.whl (9.6 kB)\n",
|
||||
"Collecting dirtyjson<2.0.0,>=1.0.8 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading dirtyjson-1.0.8-py3-none-any.whl (25 kB)\n",
|
||||
"Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (2023.6.0)\n",
|
||||
"Collecting httpx (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading httpx-0.27.0-py3-none-any.whl (75 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m75.6/75.6 kB\u001b[0m \u001b[31m6.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hCollecting llamaindex-py-client<0.2.0,>=0.1.13 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading llamaindex_py_client-0.1.13-py3-none-any.whl (107 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m108.0/108.0 kB\u001b[0m \u001b[31m10.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: nest-asyncio<2.0.0,>=1.5.8 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (1.6.0)\n",
|
||||
"Requirement already satisfied: networkx>=3.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (3.2.1)\n",
|
||||
"Requirement already satisfied: nltk<4.0.0,>=3.8.1 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (3.8.1)\n",
|
||||
"Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (1.25.2)\n",
|
||||
"Collecting openai>=1.1.0 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading openai-1.13.3-py3-none-any.whl (227 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m227.4/227.4 kB\u001b[0m \u001b[31m16.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (1.5.3)\n",
|
||||
"Requirement already satisfied: pillow>=9.0.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (9.4.0)\n",
|
||||
"Requirement already satisfied: requests>=2.31.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (2.31.0)\n",
|
||||
"Requirement already satisfied: tenacity<9.0.0,>=8.2.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (8.2.3)\n",
|
||||
"Collecting tiktoken>=0.3.3 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading tiktoken-0.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.8 MB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.8/1.8 MB\u001b[0m \u001b[31m43.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: tqdm<5.0.0,>=4.66.1 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (4.66.2)\n",
|
||||
"Requirement already satisfied: typing-extensions>=4.5.0 in /usr/local/lib/python3.10/dist-packages (from llama-index-core>=0.10.7->llama-parse) (4.10.0)\n",
|
||||
"Collecting typing-inspect>=0.8.0 (from llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)\n",
|
||||
"Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (1.3.1)\n",
|
||||
"Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (23.2.0)\n",
|
||||
"Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (1.4.1)\n",
|
||||
"Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (6.0.5)\n",
|
||||
"Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (1.9.4)\n",
|
||||
"Requirement already satisfied: async-timeout<5.0,>=4.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.6->llama-index-core>=0.10.7->llama-parse) (4.0.3)\n",
|
||||
"Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.10/dist-packages (from deprecated>=1.2.9.3->llama-index-core>=0.10.7->llama-parse) (1.14.1)\n",
|
||||
"Requirement already satisfied: pydantic>=1.10 in /usr/local/lib/python3.10/dist-packages (from llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core>=0.10.7->llama-parse) (2.6.3)\n",
|
||||
"Requirement already satisfied: anyio in /usr/local/lib/python3.10/dist-packages (from httpx->llama-index-core>=0.10.7->llama-parse) (3.7.1)\n",
|
||||
"Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx->llama-index-core>=0.10.7->llama-parse) (2024.2.2)\n",
|
||||
"Collecting httpcore==1.* (from httpx->llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading httpcore-1.0.4-py3-none-any.whl (77 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m77.8/77.8 kB\u001b[0m \u001b[31m8.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: idna in /usr/local/lib/python3.10/dist-packages (from httpx->llama-index-core>=0.10.7->llama-parse) (3.6)\n",
|
||||
"Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from httpx->llama-index-core>=0.10.7->llama-parse) (1.3.1)\n",
|
||||
"Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx->llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m5.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: click in /usr/local/lib/python3.10/dist-packages (from nltk<4.0.0,>=3.8.1->llama-index-core>=0.10.7->llama-parse) (8.1.7)\n",
|
||||
"Requirement already satisfied: joblib in /usr/local/lib/python3.10/dist-packages (from nltk<4.0.0,>=3.8.1->llama-index-core>=0.10.7->llama-parse) (1.3.2)\n",
|
||||
"Requirement already satisfied: regex>=2021.8.3 in /usr/local/lib/python3.10/dist-packages (from nltk<4.0.0,>=3.8.1->llama-index-core>=0.10.7->llama-parse) (2023.12.25)\n",
|
||||
"Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai>=1.1.0->llama-index-core>=0.10.7->llama-parse) (1.7.0)\n",
|
||||
"Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests>=2.31.0->llama-index-core>=0.10.7->llama-parse) (3.3.2)\n",
|
||||
"Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.31.0->llama-index-core>=0.10.7->llama-parse) (2.0.7)\n",
|
||||
"Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy[asyncio]>=1.4.49->llama-index-core>=0.10.7->llama-parse) (3.0.3)\n",
|
||||
"Collecting mypy-extensions>=0.3.0 (from typing-inspect>=0.8.0->llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)\n",
|
||||
"Collecting marshmallow<4.0.0,>=3.18.0 (from dataclasses-json->llama-index-core>=0.10.7->llama-parse)\n",
|
||||
" Downloading marshmallow-3.21.1-py3-none-any.whl (49 kB)\n",
|
||||
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.4/49.4 kB\u001b[0m \u001b[31m4.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
||||
"\u001b[?25hRequirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.10/dist-packages (from pandas->llama-index-core>=0.10.7->llama-parse) (2.8.2)\n",
|
||||
"Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->llama-index-core>=0.10.7->llama-parse) (2023.4)\n",
|
||||
"Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio->httpx->llama-index-core>=0.10.7->llama-parse) (1.2.0)\n",
|
||||
"Requirement already satisfied: packaging>=17.0 in /usr/local/lib/python3.10/dist-packages (from marshmallow<4.0.0,>=3.18.0->dataclasses-json->llama-index-core>=0.10.7->llama-parse) (23.2)\n",
|
||||
"Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic>=1.10->llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core>=0.10.7->llama-parse) (0.6.0)\n",
|
||||
"Requirement already satisfied: pydantic-core==2.16.3 in /usr/local/lib/python3.10/dist-packages (from pydantic>=1.10->llamaindex-py-client<0.2.0,>=0.1.13->llama-index-core>=0.10.7->llama-parse) (2.16.3)\n",
|
||||
"Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.8.1->pandas->llama-index-core>=0.10.7->llama-parse) (1.16.0)\n",
|
||||
"Installing collected packages: dirtyjson, mypy-extensions, marshmallow, h11, deprecated, typing-inspect, tiktoken, httpcore, httpx, dataclasses-json, openai, llamaindex-py-client, llama-index-core, llama-parse\n",
|
||||
"Successfully installed dataclasses-json-0.6.4 deprecated-1.2.14 dirtyjson-1.0.8 h11-0.14.0 httpcore-1.0.4 httpx-0.27.0 llama-index-core-0.10.19 llama-parse-0.3.8 llamaindex-py-client-0.1.13 marshmallow-3.21.1 mypy-extensions-1.0.0 openai-1.13.3 tiktoken-0.6.0 typing-inspect-0.9.0\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%pip install llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## API key\n",
|
||||
"\n",
|
||||
"The use of LlamaParse requires an API key which you can get here: https://cloud.llamaindex.ai/parse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-...\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Async (Notebook only)\n",
|
||||
"llama-parse is async-first, so running the code in a notebook requires the use of nest_asyncio\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Import the package"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Using llamaparse for getting better results (on Manga!)\n",
|
||||
"\n",
|
||||
"Sometimes the layout of a page is unusual and you will get sub-optimal reading order results with LlamaParse. For example, when parsing manga you expect the reading order to be right to left even if the content is in English!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's download an extract of a great manga \"The manga guide to calculus\", by Hiroyuki Kojima (https://www.amazon.com/Manga-Guide-Calculus-Hiroyuki-Kojima/dp/1593271948)\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--2024-03-13 13:57:19-- https://drive.usercontent.google.com/uc?id=1tZJhcpepLRdQFJFCFX50QIqLyLgqzZsY&export=download\n",
|
||||
"Resolving drive.usercontent.google.com (drive.usercontent.google.com)... 173.194.211.132, 2607:f8b0:400c:c10::84\n",
|
||||
"Connecting to drive.usercontent.google.com (drive.usercontent.google.com)|173.194.211.132|:443... connected.\n",
|
||||
"HTTP request sent, awaiting response... 303 See Other\n",
|
||||
"Location: https://drive.usercontent.google.com/download?id=1tZJhcpepLRdQFJFCFX50QIqLyLgqzZsY&export=download [following]\n",
|
||||
"--2024-03-13 13:57:19-- https://drive.usercontent.google.com/download?id=1tZJhcpepLRdQFJFCFX50QIqLyLgqzZsY&export=download\n",
|
||||
"Reusing existing connection to drive.usercontent.google.com:443.\n",
|
||||
"HTTP request sent, awaiting response... 200 OK\n",
|
||||
"Length: 3041634 (2.9M) [application/octet-stream]\n",
|
||||
"Saving to: ‘./manga.pdf’\n",
|
||||
"\n",
|
||||
"./manga.pdf 100%[===================>] 2.90M --.-KB/s in 0.04s \n",
|
||||
"\n",
|
||||
"2024-03-13 13:57:20 (78.6 MB/s) - ‘./manga.pdf’ saved [3041634/3041634]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"! wget \"https://drive.usercontent.google.com/uc?id=1tZJhcpepLRdQFJFCFX50QIqLyLgqzZsY&export=download\" -O ./manga.pdf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Without parsing instructions\n",
|
||||
"For the sake of comparison, let's first parse without any instructions."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id 25bf4202-78d8-4705-88cf-c616ae7c82af\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vanilaParsing = LlamaParse(result_type=\"markdown\").load_data(\"./manga.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"As you can see below, LlamaParse is not doing a great job here. It is interpreting the grid of comic panels as a table, and trying to fit the dialogue into a table. It's very hard to follow."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"The Asagake Times Sanda-Cho Distributor\n",
|
||||
"\n",
|
||||
"A newspaper distributor? do I have the wrong map?\n",
|
||||
"\n",
|
||||
"You’re looking It’s next for the Sanda-cho door. branch office? Everybody mistakes us for the office because we are larger. What Is a Function? 3\n",
|
||||
"---\n",
|
||||
"## Calculating the Derivative of a Constant, Linear, or Quadratic Function\n",
|
||||
"\n",
|
||||
"|1.|Let’s find the derivative of constant function f(x) = α. The differential coefficient of f(x) at x = a is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 (f(a + ε) - f(a)) / ε = lim ε→0 (α - α) = lim ε→0 0 = 0|\n",
|
||||
"| |Thus, the derivative of f(x) is f′(x) = 0. This makes sense, since our function is constant—the rate of change is 0.|\n",
|
||||
"\n",
|
||||
"Note: The differential coefficient of f(x) at x = a is often simply called the derivative of f(x) at x = a, or just f′(a).\n",
|
||||
"\n",
|
||||
"|2.|Let’s calculate the derivative of linear function f(x) = αx + β. The derivative of f(x) at x = α is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 (f(α + ε) - f(a)) = \n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(vanilaParsing[0].text[100:1000])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Using parsing instructions\n",
|
||||
"Let's try to parse the manga with custom instructions:\n",
|
||||
"\n",
|
||||
"\"The provided document is a manga comic book. Most pages do NOT have a title. It does not contain tables. Try to reconstruct the dialogue spoken in a cohesive way.\"\n",
|
||||
"\n",
|
||||
"To do so just pass the parsing instruction as a parameter to LlamaParse:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id 88ab273e-b2a7-4f84-8e72-e9367cf6b114\n",
|
||||
"."
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parsingInstructionManga = \"\"\"The provided document is a manga comic book. Most pages do NOT have a title.\n",
|
||||
"It does not contain tables.\n",
|
||||
"Try to reconstruct the dialogue spoken in a cohesive way.\"\"\"\n",
|
||||
"withInstructionParsing = LlamaParse(\n",
|
||||
" result_type=\"markdown\", parsing_instruction=parsingInstructionManga\n",
|
||||
").load_data(\"./manga.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's see how it compare with page 3! We encourage you to play with the target page and explore other pages. As you will see, the parsing instruction allowed LlamaParse to make sense of the document!\n",
|
||||
"\n",
|
||||
"<img src=\"https://drive.usercontent.google.com/download?id=1M87rXTIZE8d5v7aHmVZVW6gW3eDGq6ks&authuser=0\" />\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The Asagake Times Sanda-Cho Distributor\n",
|
||||
"\n",
|
||||
"A newspaper distributor? do I have the wrong map?\n",
|
||||
"\n",
|
||||
"You’re looking It’s next for the Sanda-cho door. branch office? Everybody mistakes us for the office because we are larger. What Is a Function? 3\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"------------------------------------------------------------\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# The Asagake Times\n",
|
||||
"\n",
|
||||
"Sanda-Cho Distributor\n",
|
||||
"\n",
|
||||
"A newspaper distributor?\n",
|
||||
"\n",
|
||||
"Do I have the wrong map?\n",
|
||||
"\n",
|
||||
"You're looking for the Sanda-cho branch office?\n",
|
||||
"\n",
|
||||
"It's next door.\n",
|
||||
"\n",
|
||||
"Everybody mistakes us for the office because we are larger.\n",
|
||||
"\n",
|
||||
"What Is a Function? 3\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"target_page = 1\n",
|
||||
"print(vanilaParsing[0].text.split(\"\\n---\\n\")[target_page])\n",
|
||||
"print(\"\\n\\n------------------------------------------------------------\\n\\n\")\n",
|
||||
"print(withInstructionParsing[0].text.split(\"\\n---\\n\")[target_page])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Math - doing more with parsing instuction!\n",
|
||||
"\n",
|
||||
"But this manga is about math and full of equations, why not ask the parser to output them in **LaTeX**?\n",
|
||||
"\n",
|
||||
"<img src=\"https://drive.usercontent.google.com/download?id=1tze3xcQ7axVA-vC_iZeAj_GvYcyNuYDa&authuser=0\" />"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Started parsing the file under job_id 3a055e64-d91e-484e-b9b0-99a2e637c08d\n",
|
||||
"."
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parsingInstructionMangaLatex = \"\"\"The provided document is a manga comic book. Most pages do NOT have a title.\n",
|
||||
"It does not contain tables.\n",
|
||||
"Try to reconstruct the dialogue spoken in a cohesive way.\n",
|
||||
"Output any math equation in LATEX markdown (between $$)\"\"\"\n",
|
||||
"withLatex = LlamaParse(\n",
|
||||
" result_type=\"markdown\", parsing_instruction=parsingInstructionMangaLatex\n",
|
||||
").load_data(\"./manga.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\n",
|
||||
"[Without instruction]------------------------------------------------------------\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Calculating the Derivative of a Constant, Linear, or Quadratic Function\n",
|
||||
"\n",
|
||||
"|1.|Let’s find the derivative of constant function f(x) = α. The differential coefficient of f(x) at x = a is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 (f(a + ε) - f(a)) / ε = lim ε→0 (α - α) = lim ε→0 0 = 0|\n",
|
||||
"| |Thus, the derivative of f(x) is f′(x) = 0. This makes sense, since our function is constant—the rate of change is 0.|\n",
|
||||
"\n",
|
||||
"Note: The differential coefficient of f(x) at x = a is often simply called the derivative of f(x) at x = a, or just f′(a).\n",
|
||||
"\n",
|
||||
"|2.|Let’s calculate the derivative of linear function f(x) = αx + β. The derivative of f(x) at x = α is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 (f(α + ε) - f(a)) = lim ε→0 (α(a + ε) + β - (αa + β)) = lim ε→0 α = α|\n",
|
||||
"| |Thus, the derivative of f(x) is f′(x) = α, a constant value. This result should also be intuitive—linear functions have a constant rate of change by definition.|\n",
|
||||
"\n",
|
||||
"|3.|Let’s find the derivative of f(x) = x^2, which appeared in the story. The differential coefficient of f(x) at x = a is|\n",
|
||||
"|---|---|\n",
|
||||
"| |lim ε→0 ((a + ε)^2 - a^2) / ε = lim (a^2 + 2aε + ε^2 - a^2) / ε = lim (2aε + ε^2) = lim (2a + ε) = 2a|\n",
|
||||
"| |Thus, the differential coefficient of f(x) at x = a is 2a, or f′(a) = 2a. Therefore, the derivative of f(x) is f′(x) = 2x.|\n",
|
||||
"\n",
|
||||
"## Summary\n",
|
||||
"\n",
|
||||
"- The calculation of a limit that appears in calculus is simply a formula calculating an error.\n",
|
||||
"- A limit is used to obtain a derivative.\n",
|
||||
"- The derivative is the slope of the tangent line at a given point.\n",
|
||||
"- The derivative is nothing but the rate of change.\n",
|
||||
"\n",
|
||||
"## Chapter 1 Let’s Differentiate a Function!\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"[With instruction to output math in LATEX!]------------------------------------------------------------\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Derivative of Constant, Linear, or Quadratic Function\n",
|
||||
"\n",
|
||||
"## Calculating the Derivative of a Constant, Linear, or Quadratic Function\n",
|
||||
"\n",
|
||||
"1. Let’s find the derivative of constant function f(x) = α. The differential coefficient of f(x) at x = a is\n",
|
||||
"\n",
|
||||
"$$\n",
|
||||
"\\begin{align*}\n",
|
||||
"&\\lim_{{\\varepsilon \\to 0}} \\left( \\frac{f(a + \\varepsilon) - f(a)}{\\varepsilon} \\right) = \\lim_{{\\varepsilon \\to 0}} \\frac{\\alpha - \\alpha}{\\varepsilon} = \\lim_{{\\varepsilon \\to 0}} 0 = 0 \\\\\n",
|
||||
"\\end{align*}\n",
|
||||
"$$\n",
|
||||
"Thus, the derivative of f(x) is f′(x) = 0. This makes sense, since our function is constant—the rate of change is 0.\n",
|
||||
"\n",
|
||||
"Note: The differential coefficient of f(x) at x = a is often simply called the derivative of f(x) at x = a, or just f′(a).\n",
|
||||
"\n",
|
||||
"2. Let’s calculate the derivative of linear function f(x) = αx + β. The derivative of f(x) at x = α is\n",
|
||||
"\n",
|
||||
"$$\n",
|
||||
"\\begin{align*}\n",
|
||||
"&\\lim_{{\\varepsilon \\to 0}} \\left( \\frac{f(\\alpha + \\varepsilon) - f(a)}{\\varepsilon} \\right) = \\lim_{{\\varepsilon \\to 0}} \\frac{\\alpha(a + \\varepsilon) + \\beta - (\\alpha a + \\beta)}{\\varepsilon} = \\lim_{{\\varepsilon \\to 0}} \\alpha = \\alpha \\\\\n",
|
||||
"\\end{align*}\n",
|
||||
"$$\n",
|
||||
"Thus, the derivative of f(x) is f′(x) = α, a constant value. This result should also be intuitive—linear functions have a constant rate of change by definition.\n",
|
||||
"\n",
|
||||
"3. Let’s find the derivative of f(x) = x2. The differential coefficient of f(x) at x = a is\n",
|
||||
"\n",
|
||||
"$$\n",
|
||||
"\\begin{align*}\n",
|
||||
"&\\lim_{{\\varepsilon \\to 0}} \\left( \\frac{f(a + \\varepsilon) - f(a)}{\\varepsilon} \\right) = \\lim_{{\\varepsilon \\to 0}} \\left( (a + \\varepsilon)^2 - a^2 \\right) = \\lim_{{\\varepsilon \\to 0}} 2a\\varepsilon + \\varepsilon = \\lim_{{\\varepsilon \\to 0}} (2a + \\varepsilon) = 2a \\\\\n",
|
||||
"\\end{align*}\n",
|
||||
"$$\n",
|
||||
"Thus, the differential coefficient of f(x) at x = a is 2a, or f′(a) = 2a. Therefore, the derivative of f(x) is f′(x) = 2x.\n",
|
||||
"\n",
|
||||
"### Summary\n",
|
||||
"\n",
|
||||
"- The calculation of a limit that appears in calculus is simply a formula calculating an error.\n",
|
||||
"- A limit is used to obtain a derivative.\n",
|
||||
"- The derivative is the slope of the tangent line at a given point.\n",
|
||||
"- The derivative is nothing but the rate of change.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"target_page = 2\n",
|
||||
"print(\n",
|
||||
" \"\\n\\n[Without instruction]------------------------------------------------------------\\n\\n\"\n",
|
||||
")\n",
|
||||
"print(vanilaParsing[0].text.split(\"\\n---\\n\")[target_page])\n",
|
||||
"print(\n",
|
||||
" \"\\n\\n[With instruction to output math in LATEX!]------------------------------------------------------------\\n\\n\"\n",
|
||||
")\n",
|
||||
"print(withLatex[0].text.split(\"\\n---\\n\")[target_page])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And here is the result as rendered by https://upmath.me/ .\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"<img src=\"https://drive.usercontent.google.com/download?id=1qGo5bMGYOiIC9MnprcgEByaYjU9YII2Q&authuser=0\" />\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Over this short notebook we saw how to use parsing instructions to increase the quality and accuracy of parsing with LLamaParse!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"provenance": []
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -55,10 +55,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# API access to llama-cloud\n",
|
||||
@@ -80,25 +76,7 @@
|
||||
"execution_count": null,
|
||||
"id": "IjtKDQRLrylI",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--2024-12-05 18:54:24-- https://arxiv.org/pdf/2409.18486\n",
|
||||
"Resolving arxiv.org (arxiv.org)... 151.101.67.42, 151.101.131.42, 151.101.3.42, ...\n",
|
||||
"Connecting to arxiv.org (arxiv.org)|151.101.67.42|:443... connected.\n",
|
||||
"HTTP request sent, awaiting response... 200 OK\n",
|
||||
"Length: 13986265 (13M) [application/pdf]\n",
|
||||
"Saving to: ‘o1.pdf’\n",
|
||||
"\n",
|
||||
"o1.pdf 100%[===================>] 13.34M 11.8MB/s in 1.1s \n",
|
||||
"\n",
|
||||
"2024-12-05 18:54:26 (11.8 MB/s) - ‘o1.pdf’ saved [13986265/13986265]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget \"https://arxiv.org/pdf/2409.18486\" -O \"o1.pdf\""
|
||||
]
|
||||
@@ -118,25 +96,6 @@
|
||||
"Using your own API key may incur additional costs from your model provider and could result in failed pages or documents if you do not have sufficient usage limits."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dc921729-3446-42ca-8e1b-a6fd26195ed9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.schema import TextNode\n",
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_text_nodes(json_list: List[dict]):\n",
|
||||
" text_nodes = []\n",
|
||||
" for idx, page in enumerate(json_list):\n",
|
||||
" text_node = TextNode(text=page[\"md\"], metadata={\"page\": page[\"page\"]})\n",
|
||||
" text_nodes.append(text_node)\n",
|
||||
" return text_nodes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1b5d6da6",
|
||||
@@ -163,15 +122,13 @@
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(\n",
|
||||
" result_type=\"markdown\",\n",
|
||||
" use_vendor_multimodal_model=True,\n",
|
||||
" vendor_multimodal_model_name=\"anthropic-sonnet-3.5\",\n",
|
||||
" target_pages=\"24\"\n",
|
||||
" # invalidate_cache=True\n",
|
||||
")\n",
|
||||
"json_objs = parser.get_json_result(\"o1.pdf\")\n",
|
||||
"json_list = json_objs[0][\"pages\"]\n",
|
||||
"docs = get_text_nodes(json_list)"
|
||||
"result = await parser.aparse(\"o1.pdf\")\n",
|
||||
"nodes = result.get_text_nodes(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -202,15 +159,13 @@
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser_gpt4o = LlamaParse(\n",
|
||||
" result_type=\"markdown\",\n",
|
||||
" use_vendor_multimodal_model=True,\n",
|
||||
" vendor_multimodal_model=\"openai-gpt4o\",\n",
|
||||
" target_pages=\"24\",\n",
|
||||
" # invalidate_cache=True\n",
|
||||
")\n",
|
||||
"json_objs_gpt4o = parser_gpt4o.get_json_result(\"o1.pdf\")\n",
|
||||
"json_list_gpt4o = json_objs_gpt4o[0][\"pages\"]\n",
|
||||
"docs_gpt4o = get_text_nodes(json_list_gpt4o)"
|
||||
"result = await parser_gpt4o.aparse(\"o1.pdf\")\n",
|
||||
"nodes = result.get_markdown_nodes(split_by_page=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -268,7 +223,7 @@
|
||||
],
|
||||
"source": [
|
||||
"# using Sonnet-3.5\n",
|
||||
"print(docs[0].get_content(metadata_mode=\"all\"))"
|
||||
"print(nodes[0].get_content(metadata_mode=\"all\"))"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -327,7 +282,7 @@
|
||||
],
|
||||
"source": [
|
||||
"# using GPT-4o\n",
|
||||
"print(docs_gpt4o[0].get_content(metadata_mode=\"all\"))"
|
||||
"print(nodes[0].get_content(metadata_mode=\"all\"))"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -47,11 +47,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the async code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# API access to llama-cloud\n",
|
||||
@@ -71,25 +66,7 @@
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--2024-12-05 11:40:59-- https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/10k/uber_2021.pdf\n",
|
||||
"Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 2606:50c0:8000::154, 2606:50c0:8002::154, 2606:50c0:8003::154, ...\n",
|
||||
"Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|2606:50c0:8000::154|:443... connected.\n",
|
||||
"HTTP request sent, awaiting response... 200 OK\n",
|
||||
"Length: 1880483 (1.8M) [application/octet-stream]\n",
|
||||
"Saving to: ‘./uber_2021.pdf’\n",
|
||||
"\n",
|
||||
"./uber_2021.pdf 100%[===================>] 1.79M --.-KB/s in 0.1s \n",
|
||||
"\n",
|
||||
"2024-12-05 11:40:59 (14.2 MB/s) - ‘./uber_2021.pdf’ saved [1880483/1880483]\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/10k/uber_2021.pdf' -O './uber_2021.pdf'"
|
||||
]
|
||||
@@ -119,9 +96,10 @@
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"parser = LlamaParse(target_pages=\"0,1,2\", result_type=\"markdown\")\n",
|
||||
"parser = LlamaParse(target_pages=\"0,1,2\")\n",
|
||||
"\n",
|
||||
"documents = parser.load_data(\"./uber_2021.pdf\")"
|
||||
"results = await parser.aparse(\"./uber_2021.pdf\")\n",
|
||||
"documents = results.get_text_documents(split_by_page=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,367 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# RAG for Table Comparisons with LlamaParse + LlamaIndex\n",
|
||||
"\n",
|
||||
"<a href=\"https://colab.research.google.com/github/run-llama/llama_cloud_services/blob/main/examples/parse/demo_table_comparisons.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n",
|
||||
"\n",
|
||||
"This notebook shows you how to do comparisons across both tabular and text data across multiple PDF documents.\n",
|
||||
"\n",
|
||||
"We load in multiple PDFs with embedded tables (2021 and 2020 10K filings for Apple) using LlamaParse, parse each into a hierarchy of tables/text objects, define a recursive retriever over each, and then compose both with a SubQuestionQueryEngine."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
"Install core packages, download files, parse documents."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install llama-index\n",
|
||||
"%pip install llama-index-core\n",
|
||||
"%pip install llama-index-embeddings-openai\n",
|
||||
"%pip install llama-index-question-gen-openai\n",
|
||||
"%pip install llama-index-postprocessor-flag-embedding-reranker\n",
|
||||
"%pip install git+https://github.com/FlagOpen/FlagEmbedding.git\n",
|
||||
"%pip install llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget \"https://s2.q4cdn.com/470004039/files/doc_financials/2020/ar/_10-K-2020-(As-Filed).pdf\" -O apple_2020_10k.pdf\n",
|
||||
"!wget \"https://s2.q4cdn.com/470004039/files/doc_financials/2021/q4/_10-K-2021-(As-Filed).pdf\" -O apple_2021_10k.pdf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Some OpenAI and LlamaParse details"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# llama-parse is async-first, running the async code in a notebook requires the use of nest_asyncio\n",
|
||||
"import nest_asyncio\n",
|
||||
"\n",
|
||||
"nest_asyncio.apply()\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"# API access to llama-cloud\n",
|
||||
"os.environ[\"LLAMA_CLOUD_API_KEY\"] = \"llx-\"\n",
|
||||
"\n",
|
||||
"# Using OpenAI API for embeddings/llms\n",
|
||||
"os.environ[\"OPENAI_API_KEY\"] = \"sk-\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.llms.openai import OpenAI\n",
|
||||
"from llama_index.embeddings.openai import OpenAIEmbedding\n",
|
||||
"from llama_index.core import VectorStoreIndex\n",
|
||||
"from llama_index.core import Settings\n",
|
||||
"\n",
|
||||
"embed_model = OpenAIEmbedding(model=\"text-embedding-3-small\")\n",
|
||||
"llm = OpenAI(model=\"gpt-3.5-turbo-0125\")\n",
|
||||
"\n",
|
||||
"Settings.llm = llm\n",
|
||||
"Settings.embed_model = embed_model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Using brand new `LlamaParse` PDF reader for PDF Parsing\n",
|
||||
"\n",
|
||||
"we also compare two different retrieval/query engine strategies:\n",
|
||||
"1. Using raw Markdown text as nodes for building index and apply simple query engine for generating the results;\n",
|
||||
"2. Using `MarkdownElementNodeParser` for parsing the `LlamaParse` output Markdown results and building recursive retriever query engine for generation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaParse\n",
|
||||
"\n",
|
||||
"docs_2021 = LlamaParse(result_type=\"markdown\").load_data(\"./apple_2021_10k.pdf\")\n",
|
||||
"docs_2020 = LlamaParse(result_type=\"markdown\").load_data(\"./apple_2020_10k.pdf\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Create Recursive Retriever over each Document\n",
|
||||
"\n",
|
||||
"We define a function to get a recursive retriever from each document. The steps are the following:\n",
|
||||
"- Hierarchically parse the document using our `MarkdownElementNodeParser`, which will embed/summarize embedded tables.\n",
|
||||
"- Load into a vector store. Under the hood we will automatically store links between nodes (e.g. table summary to table text).\n",
|
||||
"- Get a query engine over the vector store, which performs retrieval/synthesis. Under the hood we will automatically perform recursive retrieval if there are links."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.node_parser import MarkdownElementNodeParser\n",
|
||||
"\n",
|
||||
"node_parser = MarkdownElementNodeParser(\n",
|
||||
" llm=OpenAI(model=\"gpt-3.5-turbo-0125\"), num_workers=8\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pickle\n",
|
||||
"from llama_index.postprocessor.flag_embedding_reranker import (\n",
|
||||
" FlagEmbeddingReranker,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"reranker = FlagEmbeddingReranker(\n",
|
||||
" top_n=5,\n",
|
||||
" model=\"BAAI/bge-reranker-large\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def create_query_engine_over_doc(docs, nodes_save_path=None):\n",
|
||||
" \"\"\"Big function to go from document path -> recursive retriever.\"\"\"\n",
|
||||
" if nodes_save_path is not None and os.path.exists(nodes_save_path):\n",
|
||||
" raw_nodes = pickle.load(open(nodes_save_path, \"rb\"))\n",
|
||||
" else:\n",
|
||||
" raw_nodes = node_parser.get_nodes_from_documents(docs)\n",
|
||||
" if nodes_save_path is not None:\n",
|
||||
" pickle.dump(raw_nodes, open(nodes_save_path, \"wb\"))\n",
|
||||
"\n",
|
||||
" base_nodes, objects = node_parser.get_nodes_and_objects(raw_nodes)\n",
|
||||
"\n",
|
||||
" ### Construct Retrievers\n",
|
||||
" # construct top-level vector index + query engine\n",
|
||||
" vector_index = VectorStoreIndex(nodes=base_nodes + objects)\n",
|
||||
" query_engine = vector_index.as_query_engine(\n",
|
||||
" similarity_top_k=15, node_postprocessors=[reranker]\n",
|
||||
" )\n",
|
||||
" return query_engine, base_nodes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"query_engine_2021, nodes_2021 = create_query_engine_over_doc(\n",
|
||||
" docs_2021, nodes_save_path=\"2021_nodes.pkl\"\n",
|
||||
")\n",
|
||||
"query_engine_2020, nodes_2020 = create_query_engine_over_doc(\n",
|
||||
" docs_2020, nodes_save_path=\"2020_nodes.pkl\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_index.core.tools import QueryEngineTool, ToolMetadata\n",
|
||||
"from llama_index.core.query_engine import SubQuestionQueryEngine\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# setup base query engine as tool\n",
|
||||
"query_engine_tools = [\n",
|
||||
" QueryEngineTool(\n",
|
||||
" query_engine=query_engine_2021,\n",
|
||||
" metadata=ToolMetadata(\n",
|
||||
" name=\"apple_2021_10k\",\n",
|
||||
" description=(\"Provides information about Apple financials for year 2021\"),\n",
|
||||
" ),\n",
|
||||
" ),\n",
|
||||
" QueryEngineTool(\n",
|
||||
" query_engine=query_engine_2020,\n",
|
||||
" metadata=ToolMetadata(\n",
|
||||
" name=\"apple_2020_10k\",\n",
|
||||
" description=(\"Provides information about Apple financials for year 2020\"),\n",
|
||||
" ),\n",
|
||||
" ),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"sub_query_engine = SubQuestionQueryEngine.from_defaults(\n",
|
||||
" query_engine_tools=query_engine_tools,\n",
|
||||
" llm=llm,\n",
|
||||
" use_async=True,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Try out Some Comparisons"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generated 4 sub questions.\n",
|
||||
"\u001b[1;3;38;2;237;90;200m[apple_2021_10k] Q: What are the deferred assets in 2021?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2021_10k] Q: What are the deferred liabilities in 2021?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;11;159;203m[apple_2020_10k] Q: What are the deferred assets in 2020?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;155;135;227m[apple_2020_10k] Q: What are the deferred liabilities in 2020?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2021_10k] A: $7,200\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;155;135;227m[apple_2020_10k] A: $10,138\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;237;90;200m[apple_2021_10k] A: $25,176 million\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;11;159;203m[apple_2020_10k] A: $19,336\n",
|
||||
"\u001b[0m"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = sub_query_engine.query(\n",
|
||||
" \"Can you compare and contrast the deferred assets and liabilities in 2021 with 2020?\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"In 2021, the deferred assets increased by $5,840 million compared to 2020, while the deferred liabilities decreased by $2,938 million in the same period.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(str(response))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generated 2 sub questions.\n",
|
||||
"\u001b[1;3;38;2;237;90;200m[apple_2021_10k] Q: What is the total number of RSUs in Apple's 2021 financials?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2020_10k] Q: What is the total number of RSUs in Apple's 2020 financials?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;237;90;200m[apple_2021_10k] A: The total number of RSUs in Apple's 2021 financials is 240,427.\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2020_10k] A: The total number of RSUs in Apple's 2020 financials is 310,778.\n",
|
||||
"\u001b[0m"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = sub_query_engine.query(\n",
|
||||
" \"Can you compare and contrast the total number of RSUs in 2021 and 2020?\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generated 2 sub questions.\n",
|
||||
"\u001b[1;3;38;2;237;90;200m[apple_2021_10k] Q: What are the risk factors mentioned in the 2021 financial report of Apple?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2020_10k] Q: What are the risk factors mentioned in the 2020 financial report of Apple?\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;237;90;200m[apple_2021_10k] A: The risk factors mentioned in the 2021 financial report of Apple include risks related to COVID-19, macroeconomic and industry risks, political events, trade and international disputes, natural disasters, public health issues, industrial accidents, credit risk, fluctuations in foreign currency exchange rates, changes in tax rates and legislation, volatility in the price of the company's stock, and exposure to legal proceedings and claims.\n",
|
||||
"\u001b[0m\u001b[1;3;38;2;90;149;237m[apple_2020_10k] A: The risk factors mentioned in the 2020 financial report of Apple include the impact of the COVID-19 pandemic on the company's business operations, financial condition, and stock price; global and regional economic conditions affecting demand for products and services; competition in global markets with rapid technological changes; potential disruptions in the supply chain due to industrial accidents or public health issues; information technology system failures or network disruptions affecting business operations; risks associated with confidential information security and potential unauthorized access; fluctuations in quarterly net sales and operating results due to various factors; stock price volatility impacting investor confidence and employee retention; financial performance risks related to changes in foreign currency exchange rates affecting sales and earnings.\n",
|
||||
"\u001b[0m"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"response = sub_query_engine.query(\n",
|
||||
" \"Can you compare and contrast the risk factors in 2021 vs. 2020?\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The risk factors mentioned in the 2021 financial report of Apple include risks related to COVID-19, macroeconomic and industry risks, political events, trade and international disputes, natural disasters, public health issues, industrial accidents, credit risk, fluctuations in foreign currency exchange rates, changes in tax rates and legislation, volatility in the price of the company's stock, and exposure to legal proceedings and claims. In contrast, the risk factors mentioned in the 2020 financial report of Apple focused more on the impact of the COVID-19 pandemic on the company's business operations, financial condition, and stock price; global and regional economic conditions affecting demand for products and services; competition in global markets with rapid technological changes; potential disruptions in the supply chain due to industrial accidents or public health issues; information technology system failures or network disruptions affecting business operations; risks associated with confidential information security and potential unauthorized access; fluctuations in quarterly net sales and operating results due to various factors; stock price volatility impacting investor confidence and employee retention; financial performance risks related to changes in foreign currency exchange rates affecting sales and earnings.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(str(response))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "llama_parse",
|
||||
"language": "python",
|
||||
"name": "llama_parse"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
@@ -19,11 +19,13 @@ from llama_index.core.readers.base import BasePydanticReader
|
||||
from llama_index.core.readers.file.base import get_default_fs
|
||||
from llama_index.core.schema import Document
|
||||
|
||||
from llama_cloud_services.parse.types import JobResult
|
||||
from llama_cloud_services.parse.utils import (
|
||||
SUPPORTED_FILE_TYPES,
|
||||
ResultType,
|
||||
nest_asyncio_err,
|
||||
nest_asyncio_msg,
|
||||
make_api_request,
|
||||
)
|
||||
|
||||
# can put in a path to the file or the file bytes itself
|
||||
@@ -808,7 +810,7 @@ class LlamaParse(BasePydanticReader):
|
||||
|
||||
try:
|
||||
url = build_url(JOB_UPLOAD_ROUTE, self.organization_id, self.project_id)
|
||||
resp = await self.aclient.post(url, files=files, data=data) # type: ignore
|
||||
resp = await make_api_request(self.aclient, "POST", url, timeout=self.max_timeout, files=files, data=data) # type: ignore
|
||||
resp.raise_for_status() # this raises if status is not 2xx
|
||||
return resp.json()["id"]
|
||||
except httpx.HTTPStatusError as err: # this catches it
|
||||
@@ -992,6 +994,148 @@ class LlamaParse(BasePydanticReader):
|
||||
else:
|
||||
raise e
|
||||
|
||||
async def aparse(
|
||||
self,
|
||||
file_path: Union[List[FileInput], FileInput],
|
||||
extra_info: Optional[dict] = None,
|
||||
fs: Optional[AbstractFileSystem] = None,
|
||||
) -> Union[List["JobResult"], "JobResult"]:
|
||||
"""
|
||||
Parse the file and return a JobResult object instead of Document objects.
|
||||
|
||||
This method is similar to aload_data but returns JobResult objects that provide
|
||||
direct access to the various output formats (text, markdown, json, etc.)
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to parse. Can be a string, path, bytes, file-like object, or a list of these.
|
||||
extra_info: Additional metadata to include in the result.
|
||||
fs: Optional filesystem to use for reading files.
|
||||
|
||||
Returns:
|
||||
JobResult object or list of JobResult objects if multiple files were provided
|
||||
"""
|
||||
|
||||
if isinstance(file_path, (str, PurePosixPath, Path, bytes, BufferedIOBase)):
|
||||
job_id = await self._create_job(file_path, extra_info=extra_info, fs=fs)
|
||||
if self.verbose:
|
||||
print("Started parsing the file under job_id %s" % job_id)
|
||||
|
||||
if isinstance(file_path, (bytes, BufferedIOBase)):
|
||||
if not extra_info or "file_name" not in extra_info:
|
||||
raise ValueError(
|
||||
"file_name must be provided in extra_info when passing bytes"
|
||||
)
|
||||
file_name = extra_info["file_name"]
|
||||
else:
|
||||
file_name = str(file_path)
|
||||
|
||||
job_result = await self._get_job_result(
|
||||
job_id, ResultType.JSON.value, verbose=self.verbose
|
||||
)
|
||||
return JobResult(
|
||||
job_id=job_id,
|
||||
file_name=file_name,
|
||||
job_result=job_result,
|
||||
api_key=self.api_key,
|
||||
base_url=self.base_url,
|
||||
client=self.aclient,
|
||||
page_separator=self.page_separator or _DEFAULT_SEPARATOR,
|
||||
)
|
||||
|
||||
elif isinstance(file_path, list):
|
||||
jobs = [
|
||||
self._create_job(
|
||||
f,
|
||||
extra_info=extra_info,
|
||||
fs=fs,
|
||||
)
|
||||
for f in file_path
|
||||
]
|
||||
file_names = []
|
||||
for f in file_path:
|
||||
if isinstance(f, (bytes, BufferedIOBase)):
|
||||
if not extra_info or "file_name" not in extra_info:
|
||||
raise ValueError(
|
||||
"file_name must be provided in extra_info when passing bytes"
|
||||
)
|
||||
file_names.append(extra_info["file_name"])
|
||||
else:
|
||||
file_names.append(str(f))
|
||||
|
||||
try:
|
||||
job_ids = await run_jobs(
|
||||
jobs,
|
||||
workers=self.num_workers,
|
||||
desc="Creating parsing jobs",
|
||||
show_progress=self.show_progress,
|
||||
)
|
||||
|
||||
job_results = await run_jobs(
|
||||
[
|
||||
self._get_job_result(
|
||||
job_id, ResultType.JSON.value, verbose=self.verbose
|
||||
)
|
||||
for job_id in job_ids
|
||||
],
|
||||
workers=self.num_workers,
|
||||
desc="Getting job results",
|
||||
show_progress=self.show_progress,
|
||||
)
|
||||
|
||||
# Create JobResults just using the job_ids and job_results
|
||||
job_results = [
|
||||
JobResult(
|
||||
job_id=job_id,
|
||||
file_name=file_names[i],
|
||||
job_result=job_results[i],
|
||||
api_key=self.api_key,
|
||||
base_url=self.base_url,
|
||||
client=self.aclient,
|
||||
page_separator=self.page_separator or _DEFAULT_SEPARATOR,
|
||||
)
|
||||
for i, job_id in enumerate(job_ids)
|
||||
]
|
||||
|
||||
return job_results
|
||||
|
||||
except RuntimeError as e:
|
||||
if nest_asyncio_err in str(e):
|
||||
raise RuntimeError(nest_asyncio_msg)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise ValueError(
|
||||
"The input file_path must be a string or a list of strings."
|
||||
)
|
||||
|
||||
def parse(
|
||||
self,
|
||||
file_path: Union[List[FileInput], FileInput],
|
||||
extra_info: Optional[dict] = None,
|
||||
fs: Optional[AbstractFileSystem] = None,
|
||||
) -> Union[List["JobResult"], "JobResult"]:
|
||||
"""
|
||||
Parse the file and return a JobResult object instead of Document objects.
|
||||
|
||||
This method is similar to load_data but returns JobResult objects that provide
|
||||
direct access to the various output formats (text, markdown, json, etc.)
|
||||
|
||||
Args:
|
||||
file_path: Path to the file to parse. Can be a string, path, bytes, file-like object, or a list of these.
|
||||
extra_info: Additional metadata to include in the result.
|
||||
fs: Optional filesystem to use for reading files.
|
||||
|
||||
Returns:
|
||||
JobResult object or list of JobResult objects if multiple files were provided
|
||||
"""
|
||||
try:
|
||||
return asyncio_run(self.aparse(file_path, extra_info, fs=fs))
|
||||
except RuntimeError as e:
|
||||
if nest_asyncio_err in str(e):
|
||||
raise RuntimeError(nest_asyncio_msg)
|
||||
else:
|
||||
raise e
|
||||
|
||||
async def _aget_json(
|
||||
self, file_path: FileInput, extra_info: Optional[dict] = None
|
||||
) -> List[dict]:
|
||||
@@ -1059,6 +1203,14 @@ class LlamaParse(BasePydanticReader):
|
||||
else:
|
||||
raise e
|
||||
|
||||
def get_json(
|
||||
self,
|
||||
file_path: Union[List[FileInput], FileInput],
|
||||
extra_info: Optional[dict] = None,
|
||||
) -> List[dict]:
|
||||
"""Load data from the input path."""
|
||||
return self.get_json_result(file_path, extra_info)
|
||||
|
||||
async def aget_assets(
|
||||
self, json_result: List[dict], download_path: str, asset_key: str
|
||||
) -> List[dict]:
|
||||
@@ -1097,7 +1249,9 @@ class LlamaParse(BasePydanticReader):
|
||||
|
||||
with open(asset_path, "wb") as f:
|
||||
asset_url = f"{self.base_url}/api/parsing/job/{job_id}/result/image/{asset_name}"
|
||||
resp = await client.get(asset_url)
|
||||
resp = await make_api_request(
|
||||
client, "GET", asset_url, timeout=self.max_timeout
|
||||
)
|
||||
resp.raise_for_status()
|
||||
f.write(resp.content)
|
||||
assets.append(asset)
|
||||
@@ -1182,7 +1336,9 @@ class LlamaParse(BasePydanticReader):
|
||||
xlsx_url = (
|
||||
f"{self.base_url}/api/parsing/job/{job_id}/result/raw/xlsx"
|
||||
)
|
||||
res = await client.get(xlsx_url)
|
||||
res = await make_api_request(
|
||||
client, "GET", xlsx_url, timeout=self.max_timeout
|
||||
)
|
||||
res.raise_for_status()
|
||||
f.write(res.content)
|
||||
xlsx_list.append(xlsx)
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
import httpx
|
||||
import os
|
||||
import re
|
||||
from pydantic import BaseModel, Field, SerializeAsAny
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from llama_cloud_services.parse.utils import make_api_request
|
||||
from llama_index.core.async_utils import asyncio_run
|
||||
from llama_index.core.schema import Document, ImageDocument, ImageNode, TextNode
|
||||
|
||||
PAGE_REGEX = r"page[-_](\d+)\.jpg$"
|
||||
|
||||
|
||||
class JobMetadata(BaseModel):
|
||||
"""Metadata about the job."""
|
||||
|
||||
job_credits_usage: int = Field(
|
||||
default_factory=dict, description="The credits usage for the job."
|
||||
)
|
||||
job_pages: int = Field(description="The number of pages in the job.")
|
||||
job_auto_mode_triggered_pages: int = Field(
|
||||
description="The number of pages that triggered auto mode (thus increasing the cost)."
|
||||
)
|
||||
job_is_cache_hit: bool = Field(description="Whether the job was a cache hit.")
|
||||
|
||||
|
||||
class BBox(BaseModel):
|
||||
"""A bounding box."""
|
||||
|
||||
x: float = Field(description="The x-coordinate of the bounding box.")
|
||||
y: float = Field(description="The y-coordinate of the bounding box.")
|
||||
w: float = Field(description="The width of the bounding box.")
|
||||
h: float = Field(description="The height of the bounding box.")
|
||||
|
||||
|
||||
class PageItem(BaseModel):
|
||||
"""An item in a page."""
|
||||
|
||||
type: str = Field(description="The type of the item.")
|
||||
lvl: Optional[int] = Field(
|
||||
default=None, description="The level of indentation of the item."
|
||||
)
|
||||
value: Optional[str] = Field(
|
||||
default=None, description="The text content of the item."
|
||||
)
|
||||
md: Optional[str] = Field(
|
||||
default=None, description="The markdown-formatted content of the item."
|
||||
)
|
||||
rows: Optional[List[List[str]]] = Field(
|
||||
default=None, description="The rows of the item."
|
||||
)
|
||||
bBox: BBox = Field(description="The bounding box of the item.")
|
||||
|
||||
|
||||
class ImageItem(BaseModel):
|
||||
"""An image in a page."""
|
||||
|
||||
name: str = Field(description="The name of the image.")
|
||||
height: float = Field(description="The height of the image.")
|
||||
width: float = Field(description="The width of the image.")
|
||||
x: float = Field(description="The x-coordinate of the image.")
|
||||
y: float = Field(description="The y-coordinate of the image.")
|
||||
original_width: int = Field(description="The original width of the image.")
|
||||
original_height: int = Field(description="The original height of the image.")
|
||||
type: Optional[str] = Field(default=None, description="The type of the image.")
|
||||
|
||||
|
||||
class LayoutItem(BaseModel):
|
||||
"""The layout of a page."""
|
||||
|
||||
image: str = Field(description="The name of the image containing the layout item")
|
||||
confidence: float = Field(description="The confidence of the layout item.")
|
||||
label: str = Field(description="The label of the layout item.")
|
||||
bbox: BBox = Field(description="The bounding box of the layout item.")
|
||||
isLikelyNoise: bool = Field(description="Whether the layout item is likely noise.")
|
||||
|
||||
|
||||
class ChartItem(BaseModel):
|
||||
"""A chart in a page."""
|
||||
|
||||
name: str = Field(description="The name of the chart.")
|
||||
x: float = Field(description="The x-coordinate of the chart.")
|
||||
y: float = Field(description="The y-coordinate of the chart.")
|
||||
width: float = Field(description="The width of the chart.")
|
||||
height: float = Field(description="The height of the chart.")
|
||||
|
||||
|
||||
class Page(BaseModel):
|
||||
"""A page of the document."""
|
||||
|
||||
page: int = Field(description="The page number.")
|
||||
text: str = Field(description="The text of the page.")
|
||||
md: str = Field(description="The markdown of the page.")
|
||||
images: List[ImageItem] = Field(
|
||||
default_factory=list,
|
||||
description="The names of the image IDs in the page, including both objects and page screenshots.",
|
||||
)
|
||||
charts: List[ChartItem] = Field(
|
||||
default_factory=list, description="The charts in the page."
|
||||
)
|
||||
tables: List[str] = Field(
|
||||
default_factory=list, description="The names of the table IDs in the page."
|
||||
)
|
||||
layout: List[LayoutItem] = Field(
|
||||
default_factory=list, description="The layout of the page."
|
||||
)
|
||||
items: List[PageItem] = Field(
|
||||
default_factory=list, description="The items in the page."
|
||||
)
|
||||
status: str = Field(description="The status of the page.")
|
||||
links: List[SerializeAsAny[Any]] = Field(
|
||||
default_factory=list, description="The links in the page."
|
||||
)
|
||||
width: float = Field(description="The width of the page.")
|
||||
height: float = Field(description="The height of the page.")
|
||||
triggeredAutoMode: bool = Field(
|
||||
description="Whether the page triggered auto mode (thus increasing the cost)."
|
||||
)
|
||||
parsingMode: str = Field(description="The parsing mode used for the page.")
|
||||
structuredData: Optional[Dict[str, Any]] = Field(
|
||||
description="The structured data of the page."
|
||||
)
|
||||
noStructuredContent: bool = Field(
|
||||
description="Whether the page has no structured data."
|
||||
)
|
||||
noTextContent: bool = Field(description="Whether the page has no text content.")
|
||||
|
||||
|
||||
class JobResult(BaseModel):
|
||||
"""The raw JSON result from the LlamaParse API."""
|
||||
|
||||
pages: List[Page] = Field(description="The pages of the document.")
|
||||
job_metadata: JobMetadata = Field(description="The metadata of the job.")
|
||||
file_name: str = Field(description="The path to the file that was parsed.")
|
||||
job_id: str = Field(description="The ID of the job.")
|
||||
is_done: bool = Field(default=False, description="Whether the job is done.")
|
||||
error: Optional[str] = Field(
|
||||
default=None, description="The error message if the job failed."
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
job_id: str,
|
||||
file_name: str,
|
||||
job_result: Dict[str, Any],
|
||||
api_key: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
client: Optional[httpx.AsyncClient] = None,
|
||||
page_separator: str = "\n\n",
|
||||
):
|
||||
"""
|
||||
Initialize JobResult with job_id and job_result.
|
||||
|
||||
Args:
|
||||
job_id: The job ID of the parsing task
|
||||
job_result: The JSON response from the parsing job or a JobResult instance (optional)
|
||||
api_key: The API key for the LlamaParse API
|
||||
base_url: The base URL of the Llama Parsing API
|
||||
page_separator: The separator that was used to define page splits in the result
|
||||
"""
|
||||
super().__init__(job_id=job_id, file_name=file_name, **job_result)
|
||||
|
||||
self._api_key = api_key or os.environ.get("LLAMA_CLOUD_API_KEY", "")
|
||||
self._base_url = base_url or os.environ.get(
|
||||
"LLAMA_CLOUD_BASE_URL", "https://api.llama-parse.ai"
|
||||
)
|
||||
self._client = client or httpx.AsyncClient()
|
||||
self._client.base_url = self._base_url
|
||||
self._client.headers["Authorization"] = f"Bearer {self._api_key}"
|
||||
self._page_separator = page_separator
|
||||
|
||||
def get_text_documents(self, split_by_page: bool = False) -> List[Document]:
|
||||
"""
|
||||
Get the documents from the job.
|
||||
|
||||
Args:
|
||||
split_by_page: Whether to split the pages into separate documents
|
||||
"""
|
||||
if split_by_page:
|
||||
return [
|
||||
Document(
|
||||
text=page.text,
|
||||
metadata={"page_number": page.page, "file_name": self.file_name},
|
||||
)
|
||||
for page in self.pages
|
||||
]
|
||||
else:
|
||||
text = self._page_separator.join([page.text for page in self.pages])
|
||||
return [Document(text=text, metadata={"file_name": self.file_name})]
|
||||
|
||||
async def aget_text_documents(self, split_by_page: bool = False) -> List[Document]:
|
||||
"""
|
||||
Get the documents from the job.
|
||||
|
||||
Args:
|
||||
split_by_page: Whether to split the pages into separate documents
|
||||
"""
|
||||
# No async needed, but here for consistency
|
||||
return self.get_text_documents(split_by_page)
|
||||
|
||||
def get_text_nodes(self, split_by_page: bool = False) -> List[TextNode]:
|
||||
"""
|
||||
Get the text nodes from the job.
|
||||
"""
|
||||
documents = self.get_text_documents(split_by_page)
|
||||
return [TextNode(text=doc.text, metadata=doc.metadata) for doc in documents]
|
||||
|
||||
async def aget_text_nodes(self, split_by_page: bool = False) -> List[TextNode]:
|
||||
"""
|
||||
Get the text nodes from the job.
|
||||
"""
|
||||
documents = await self.aget_text_documents(split_by_page)
|
||||
return [TextNode(text=doc.text, metadata=doc.metadata) for doc in documents]
|
||||
|
||||
def get_markdown_documents(self, split_by_page: bool = False) -> List[Document]:
|
||||
"""
|
||||
Get the markdown documents from the job.
|
||||
|
||||
Args:
|
||||
split_by_page: Whether to split the pages into separate documents
|
||||
"""
|
||||
if split_by_page:
|
||||
return [
|
||||
Document(
|
||||
text=page.md,
|
||||
metadata={"page_number": page.page, "file_name": self.file_name},
|
||||
)
|
||||
for page in self.pages
|
||||
]
|
||||
else:
|
||||
return [
|
||||
Document(
|
||||
text=self._page_separator.join([page.md for page in self.pages]),
|
||||
metadata={"file_name": self.file_name},
|
||||
)
|
||||
]
|
||||
|
||||
async def aget_markdown_documents(
|
||||
self, split_by_page: bool = False
|
||||
) -> List[Document]:
|
||||
"""
|
||||
Get the markdown documents from the job.
|
||||
|
||||
Args:
|
||||
split_by_page: Whether to split the pages into separate documents
|
||||
"""
|
||||
# No async needed, but here for consistency
|
||||
return self.get_markdown_documents(split_by_page)
|
||||
|
||||
def get_markdown_nodes(self, split_by_page: bool = False) -> List[TextNode]:
|
||||
"""
|
||||
Get the markdown nodes from the job.
|
||||
|
||||
Args:
|
||||
split_by_page: Whether to split the pages into separate documents
|
||||
"""
|
||||
documents = self.get_markdown_documents(split_by_page)
|
||||
return [TextNode(text=doc.text, metadata=doc.metadata) for doc in documents]
|
||||
|
||||
async def aget_markdown_nodes(self, split_by_page: bool = False) -> List[TextNode]:
|
||||
"""
|
||||
Get the markdown nodes from the job.
|
||||
|
||||
Args:
|
||||
split_by_page: Whether to split the pages into separate documents
|
||||
"""
|
||||
documents = await self.aget_markdown_documents(split_by_page)
|
||||
return [TextNode(text=doc.text, metadata=doc.metadata) for doc in documents]
|
||||
|
||||
async def _get_image_document_with_bytes(
|
||||
self, image: ImageItem, page: Page
|
||||
) -> ImageDocument:
|
||||
image_data = await self.aget_image_data(image.name)
|
||||
|
||||
return ImageDocument(
|
||||
image=image_data,
|
||||
metadata={
|
||||
"page_number": page.page,
|
||||
"file_name": self.file_name,
|
||||
"width": image.original_width,
|
||||
"height": image.original_height,
|
||||
"x": image.x,
|
||||
"y": image.y,
|
||||
},
|
||||
excluded_embed_metadata_keys=["width", "height", "x", "y"],
|
||||
excluded_llm_metadata_keys=["width", "height", "x", "y"],
|
||||
)
|
||||
|
||||
async def _get_image_document_with_path(
|
||||
self, image: ImageItem, page: Page, image_download_dir: str
|
||||
) -> ImageDocument:
|
||||
image_path = await self.asave_image(image.name, image_download_dir)
|
||||
|
||||
return ImageDocument(
|
||||
image_path=image_path,
|
||||
metadata={
|
||||
"page_number": page.page,
|
||||
"file_name": self.file_name,
|
||||
"width": image.original_width,
|
||||
"height": image.original_height,
|
||||
"x": image.x,
|
||||
"y": image.y,
|
||||
},
|
||||
excluded_embed_metadata_keys=["width", "height", "x", "y"],
|
||||
excluded_llm_metadata_keys=["width", "height", "x", "y"],
|
||||
)
|
||||
|
||||
def get_image_documents(
|
||||
self,
|
||||
include_screenshot_images: bool = True,
|
||||
include_object_images: bool = True,
|
||||
image_download_dir: Optional[str] = None,
|
||||
) -> List[ImageDocument]:
|
||||
"""
|
||||
Get the image documents from the job.
|
||||
|
||||
Args:
|
||||
include_screenshot_images (bool):
|
||||
Whether to include screenshot images. Default is True.
|
||||
include_object_images (bool):
|
||||
Whether to include object images. Default is True.
|
||||
image_download_dir (Optional[str]):
|
||||
The directory to save the images to. If not provided, the images will be loaded into memory.
|
||||
Default is None.
|
||||
"""
|
||||
return asyncio_run(
|
||||
self.aget_image_documents(
|
||||
include_screenshot_images, include_object_images, image_download_dir
|
||||
)
|
||||
)
|
||||
|
||||
async def aget_image_documents(
|
||||
self,
|
||||
include_screenshot_images: bool = True,
|
||||
include_object_images: bool = True,
|
||||
image_download_dir: Optional[str] = None,
|
||||
) -> List[ImageDocument]:
|
||||
"""
|
||||
Get the image documents from the job.
|
||||
|
||||
Args:
|
||||
include_screenshot_images (bool):
|
||||
Whether to include screenshot images. Default is True.
|
||||
include_object_images (bool):
|
||||
Whether to include object images. Default is True.
|
||||
image_download_dir (Optional[str]):
|
||||
The directory to save the images to. If not provided, the images will be loaded into memory.
|
||||
Default is None.
|
||||
"""
|
||||
documents = []
|
||||
for page in self.pages:
|
||||
for image in page.images:
|
||||
is_screenshot = re.search(PAGE_REGEX, image.name) is not None
|
||||
|
||||
# Skip images that don't match the inclusion criteria
|
||||
if (is_screenshot and not include_screenshot_images) or (
|
||||
not is_screenshot and not include_object_images
|
||||
):
|
||||
continue
|
||||
|
||||
# Get image document using appropriate method based on download_dir
|
||||
get_document = (
|
||||
self._get_image_document_with_path
|
||||
if image_download_dir
|
||||
else self._get_image_document_with_bytes
|
||||
)
|
||||
|
||||
documents.append(
|
||||
await get_document(image, page, image_download_dir) # type: ignore
|
||||
if image_download_dir
|
||||
else await get_document(image, page) # type: ignore
|
||||
)
|
||||
|
||||
return documents
|
||||
|
||||
def get_image_nodes(
|
||||
self,
|
||||
include_screenshot_images: bool = True,
|
||||
include_object_images: bool = True,
|
||||
image_download_dir: Optional[str] = None,
|
||||
) -> List[ImageNode]:
|
||||
"""
|
||||
Get the image nodes from the job.
|
||||
|
||||
Args:
|
||||
include_screenshot_images (bool):
|
||||
Whether to include screenshot images. Default is True.
|
||||
include_object_images (bool):
|
||||
Whether to include object images. Default is True.
|
||||
image_download_dir (Optional[str]):
|
||||
The directory to save the images to. If not provided, the images will be loaded into memory.
|
||||
Default is None.
|
||||
"""
|
||||
documents = self.get_image_documents(
|
||||
include_screenshot_images, include_object_images, image_download_dir
|
||||
)
|
||||
return [
|
||||
ImageNode(
|
||||
image=doc.image,
|
||||
image_path=doc.image_path,
|
||||
image_url=doc.image_url,
|
||||
metadata=doc.metadata,
|
||||
)
|
||||
for doc in documents
|
||||
]
|
||||
|
||||
async def aget_image_nodes(
|
||||
self,
|
||||
include_screenshot_images: bool = True,
|
||||
include_object_images: bool = True,
|
||||
image_download_dir: Optional[str] = None,
|
||||
) -> List[ImageNode]:
|
||||
"""
|
||||
Get the image nodes from the job.
|
||||
|
||||
Args:
|
||||
include_screenshot_images (bool):
|
||||
Whether to include screenshot images. Default is True.
|
||||
include_object_images (bool):
|
||||
Whether to include object images. Default is True.
|
||||
image_download_dir (Optional[str]):
|
||||
The directory to save the images to. If not provided, the images will be loaded into memory.
|
||||
Default is None.
|
||||
"""
|
||||
documents = await self.aget_image_documents(
|
||||
include_screenshot_images, include_object_images, image_download_dir
|
||||
)
|
||||
return [
|
||||
ImageNode(
|
||||
image=doc.image,
|
||||
image_path=doc.image_path,
|
||||
image_url=doc.image_url,
|
||||
metadata=doc.metadata,
|
||||
)
|
||||
for doc in documents
|
||||
]
|
||||
|
||||
async def aget_image_data(self, image_name: str) -> bytes:
|
||||
"""
|
||||
Get image data by name using the job ID.
|
||||
|
||||
Args:
|
||||
image_name: The name of the image to fetch
|
||||
|
||||
Returns:
|
||||
The image data as bytes
|
||||
"""
|
||||
url = f"{self._base_url}/api/v1/parsing/job/{self.job_id}/result/image/{image_name}"
|
||||
response = await make_api_request(self._client, "GET", url)
|
||||
return response.content
|
||||
|
||||
def get_image_data(self, image_name: str) -> bytes:
|
||||
"""
|
||||
Get image data by name using the job ID (synchronous version).
|
||||
|
||||
Args:
|
||||
image_name: The name of the image to fetch
|
||||
|
||||
Returns:
|
||||
The image data as bytes
|
||||
"""
|
||||
return asyncio_run(self.aget_image_data(image_name))
|
||||
|
||||
async def aget_xlsx_data(self) -> bytes:
|
||||
"""
|
||||
Get the XLSX data for the job.
|
||||
|
||||
Returns:
|
||||
The XLSX data as bytes
|
||||
"""
|
||||
url = f"{self._base_url}/api/v1/parsing/job/{self.job_id}/result/xlsx"
|
||||
response = await make_api_request(self._client, "GET", url)
|
||||
return response.content
|
||||
|
||||
def get_xlsx_data(self) -> bytes:
|
||||
"""
|
||||
Get the XLSX data for the job (synchronous version).
|
||||
|
||||
Returns:
|
||||
The XLSX data as bytes
|
||||
"""
|
||||
return asyncio_run(self.aget_xlsx_data())
|
||||
|
||||
async def asave_image(self, image_name: str, output_dir: str) -> str:
|
||||
"""
|
||||
Save an image to a file.
|
||||
|
||||
Args:
|
||||
image_name: The name of the image to fetch
|
||||
output_dir: The directory to save the image to
|
||||
|
||||
Returns:
|
||||
The path to the saved image
|
||||
"""
|
||||
image_data = await self.aget_image_data(image_name)
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Save image to file
|
||||
output_path = os.path.join(output_dir, image_name)
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(image_data)
|
||||
|
||||
return output_path
|
||||
|
||||
def save_image(self, image_name: str, output_dir: str) -> str:
|
||||
"""
|
||||
Save an image to a file (synchronous version).
|
||||
|
||||
Args:
|
||||
image_name: The name of the image to fetch
|
||||
output_dir: The directory to save the image to
|
||||
|
||||
Returns:
|
||||
The path to the saved image
|
||||
"""
|
||||
return asyncio_run(self.asave_image(image_name, output_dir))
|
||||
|
||||
def get_image_names(self) -> List[str]:
|
||||
"""
|
||||
Get the names of all images in the job.
|
||||
|
||||
Returns:
|
||||
A list of image names
|
||||
"""
|
||||
return [image.name for page in self.pages for image in page.images]
|
||||
|
||||
async def asave_all_images(self, output_dir: str) -> List[str]:
|
||||
"""
|
||||
Save all images to files.
|
||||
|
||||
Args:
|
||||
output_dir: The directory to save the images to
|
||||
|
||||
Returns:
|
||||
A list of paths to the saved images
|
||||
"""
|
||||
image_names = self.get_image_names()
|
||||
saved_paths = []
|
||||
|
||||
for name in image_names:
|
||||
path = await self.asave_image(name, output_dir)
|
||||
saved_paths.append(path)
|
||||
|
||||
return saved_paths
|
||||
|
||||
def save_all_images(self, output_dir: str) -> List[str]:
|
||||
"""
|
||||
Save all images to files (synchronous version).
|
||||
|
||||
Args:
|
||||
output_dir: The directory to save the images to
|
||||
|
||||
Returns:
|
||||
A list of paths to the saved images
|
||||
"""
|
||||
return asyncio_run(self.asave_all_images(output_dir))
|
||||
@@ -1,4 +1,16 @@
|
||||
import httpx
|
||||
import logging
|
||||
from enum import Enum
|
||||
from tenacity import (
|
||||
retry,
|
||||
stop_after_attempt,
|
||||
wait_exponential,
|
||||
retry_if_exception,
|
||||
before_sleep_log,
|
||||
)
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Asyncio error messages
|
||||
nest_asyncio_err = "cannot be called from a running event loop"
|
||||
@@ -209,3 +221,68 @@ SUPPORTED_FILE_TYPES = [
|
||||
".wav",
|
||||
".webm",
|
||||
]
|
||||
|
||||
|
||||
def should_retry(exception: Exception) -> bool:
|
||||
"""Check if the exception should be retried.
|
||||
|
||||
Args:
|
||||
exception: The exception to check.
|
||||
"""
|
||||
# Retry on connection errors (network issues)
|
||||
if isinstance(
|
||||
exception,
|
||||
(
|
||||
httpx.ConnectError,
|
||||
httpx.ConnectTimeout,
|
||||
httpx.ReadTimeout,
|
||||
httpx.WriteTimeout,
|
||||
httpx.RemoteProtocolError,
|
||||
),
|
||||
):
|
||||
return True
|
||||
|
||||
# Retry on specific HTTP status codes
|
||||
if isinstance(exception, httpx.HTTPStatusError):
|
||||
status_code = exception.response.status_code
|
||||
# Retry on rate limiting or temporary server errors
|
||||
return status_code in (429, 500, 502, 503, 504)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
async def make_api_request(
|
||||
client: httpx.AsyncClient,
|
||||
method: str,
|
||||
url: str,
|
||||
timeout: float = 60.0,
|
||||
max_retries: int = 5,
|
||||
**httpx_kwargs: Any,
|
||||
) -> httpx.Response:
|
||||
"""Make an retrying API request to the LlamaParse API.
|
||||
|
||||
Args:
|
||||
client: The httpx.AsyncClient to use for the request.
|
||||
url: The URL to request.
|
||||
headers: The headers to include in the request.
|
||||
timeout: The timeout for the request.
|
||||
max_retries: The maximum number of retries for the request.
|
||||
"""
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(max_retries),
|
||||
wait=wait_exponential(multiplier=1, min=4, max=timeout),
|
||||
retry=retry_if_exception(should_retry),
|
||||
before_sleep=before_sleep_log(logger, logging.WARNING),
|
||||
)
|
||||
async def _make_request(url: str, **httpx_kwargs: Any) -> httpx.Response:
|
||||
if method == "GET":
|
||||
response = await client.get(url, **httpx_kwargs)
|
||||
elif method == "POST":
|
||||
response = await client.post(url, **httpx_kwargs)
|
||||
else:
|
||||
raise ValueError(f"Invalid method: {method}")
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
||||
return await _make_request(url, **httpx_kwargs)
|
||||
|
||||
@@ -43,42 +43,63 @@ llama-parse my_file.pdf --output-raw-json --output-file output.json
|
||||
You can also create simple scripts:
|
||||
|
||||
```python
|
||||
import nest_asyncio
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
from llama_cloud_services import LlamaParse
|
||||
|
||||
parser = LlamaParse(
|
||||
api_key="llx-...", # can also be set in your env as LLAMA_CLOUD_API_KEY
|
||||
result_type="markdown", # "markdown" and "text" are available
|
||||
num_workers=4, # if multiple files passed, split in `num_workers` API calls
|
||||
verbose=True,
|
||||
language="en", # Optionally you can define a language, default=en
|
||||
)
|
||||
|
||||
# sync
|
||||
documents = parser.load_data("./my_file.pdf")
|
||||
result = parser.parse("./my_file.pdf")
|
||||
|
||||
# sync batch
|
||||
documents = parser.load_data(["./my_file1.pdf", "./my_file2.pdf"])
|
||||
results = parser.parse(["./my_file1.pdf", "./my_file2.pdf"])
|
||||
|
||||
# async
|
||||
documents = await parser.aload_data("./my_file.pdf")
|
||||
result = await parser.aparse("./my_file.pdf")
|
||||
|
||||
# async batch
|
||||
documents = await parser.aload_data(["./my_file1.pdf", "./my_file2.pdf"])
|
||||
results = await parser.aparse(["./my_file1.pdf", "./my_file2.pdf"])
|
||||
```
|
||||
|
||||
The result object is a fully typed `JobResult` object, and you can interact with it to parse and transform various parts of the result:
|
||||
|
||||
```python
|
||||
# get the llama-index markdown documents
|
||||
markdown_documents = result.get_markdown_documents(split_by_page=True)
|
||||
|
||||
# get the llama-index text documents
|
||||
text_documents = result.get_text_documents(split_by_page=False)
|
||||
|
||||
# get the image documents
|
||||
image_documents = result.get_image_documents(
|
||||
include_screenshot_images=True,
|
||||
include_object_images=False,
|
||||
# Optional: download the images to a directory
|
||||
# (default is to return the image bytes in ImageDocument objects)
|
||||
image_download_dir="./images",
|
||||
)
|
||||
|
||||
# access the raw job result
|
||||
# Items will vary based on the parser configuration
|
||||
for page in result.pages:
|
||||
print(page.text)
|
||||
print(page.md)
|
||||
print(page.images)
|
||||
print(page.layout)
|
||||
print(page.structuredData)
|
||||
```
|
||||
|
||||
See more details about the result object in the [example notebook](./examples/parse/demo_json_tour.ipynb).
|
||||
|
||||
## Using with file object
|
||||
|
||||
You can parse a file object directly:
|
||||
|
||||
```python
|
||||
import nest_asyncio
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
from llama_cloud_services import LlamaParse
|
||||
|
||||
parser = LlamaParse(
|
||||
@@ -94,13 +115,13 @@ extra_info = {"file_name": file_name}
|
||||
|
||||
with open(f"./{file_name}", "rb") as f:
|
||||
# must provide extra_info with file_name key with passing file object
|
||||
documents = parser.load_data(f, extra_info=extra_info)
|
||||
result = parser.parse(f, extra_info=extra_info)
|
||||
|
||||
# you can also pass file bytes directly
|
||||
with open(f"./{file_name}", "rb") as f:
|
||||
file_bytes = f.read()
|
||||
# must provide extra_info with file_name key with passing file bytes
|
||||
documents = parser.load_data(file_bytes, extra_info=extra_info)
|
||||
result = parser.parse(file_bytes, extra_info=extra_info)
|
||||
```
|
||||
|
||||
## Using with `SimpleDirectoryReader`
|
||||
@@ -108,10 +129,6 @@ with open(f"./{file_name}", "rb") as f:
|
||||
You can also integrate the parser as the default PDF loader in `SimpleDirectoryReader`:
|
||||
|
||||
```python
|
||||
import nest_asyncio
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
from llama_cloud_services import LlamaParse
|
||||
from llama_index.core import SimpleDirectoryReader
|
||||
|
||||
@@ -136,6 +153,7 @@ Several end-to-end indexing examples can be found in the examples folder
|
||||
- [Getting Started](examples/parse/demo_basic.ipynb)
|
||||
- [Advanced RAG Example](examples/parse/demo_advanced.ipynb)
|
||||
- [Raw API Usage](examples/parse/demo_api.ipynb)
|
||||
- [Result Object Tour](examples/parse/demo_json_tour.ipynb)
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import tempfile
|
||||
import os
|
||||
import pytest
|
||||
from llama_cloud_services import LlamaParse
|
||||
from llama_cloud_services.parse.types import JobResult
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def file_path() -> str:
|
||||
return "tests/test_files/attention_is_all_you_need.pdf"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def chart_file_path() -> str:
|
||||
return "tests/test_files/attention_is_all_you_need_chart.pdf"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
os.environ.get("LLAMA_CLOUD_API_KEY", "") == "",
|
||||
reason="LLAMA_CLOUD_API_KEY not set",
|
||||
)
|
||||
async def test_basic_parse_result(file_path: str):
|
||||
parser = LlamaParse(
|
||||
take_screenshot=True,
|
||||
auto_mode=True,
|
||||
fast_mode=False,
|
||||
)
|
||||
result = await parser.aparse(file_path)
|
||||
|
||||
assert isinstance(result, JobResult)
|
||||
assert result.job_id is not None
|
||||
assert result.file_name == file_path
|
||||
assert len(result.pages) > 0
|
||||
|
||||
assert result.pages[0].text is not None
|
||||
assert len(result.pages[0].text) > 0
|
||||
|
||||
assert result.pages[0].md is not None
|
||||
assert len(result.pages[0].md) > 0
|
||||
|
||||
assert result.pages[0].md != result.pages[0].text
|
||||
|
||||
assert len(result.pages[0].images) > 0
|
||||
assert result.pages[0].images[0].name is not None
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
file_names = await result.asave_all_images(temp_dir)
|
||||
assert len(file_names) > 0
|
||||
for file_name in file_names:
|
||||
assert os.path.exists(file_name)
|
||||
assert os.path.getsize(file_name) > 0
|
||||
|
||||
assert result.job_metadata is not None
|
||||
|
||||
text_documents = result.get_text_documents(
|
||||
split_by_page=True,
|
||||
)
|
||||
assert len(text_documents) > 0
|
||||
assert text_documents[0].text is not None
|
||||
assert len(text_documents[0].text) > 0
|
||||
|
||||
markdown_documents = result.get_markdown_documents(
|
||||
split_by_page=True,
|
||||
)
|
||||
assert len(markdown_documents) > 0
|
||||
assert markdown_documents[0].text is not None
|
||||
assert len(markdown_documents[0].text) > 0
|
||||
|
||||
image_documents = await result.aget_image_documents(
|
||||
include_screenshot_images=True,
|
||||
include_object_images=False,
|
||||
)
|
||||
assert len(image_documents) > 0
|
||||
assert image_documents[0].image is not None
|
||||
assert len(image_documents[0].resolve_image().getvalue()) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skip(
|
||||
reason="TODO: I don't actually know how to trigger links in the output."
|
||||
)
|
||||
async def test_link_parse_result(file_path: str):
|
||||
parser = LlamaParse(
|
||||
annotate_links=True,
|
||||
)
|
||||
result = await parser.aparse(file_path)
|
||||
|
||||
assert isinstance(result, JobResult)
|
||||
assert len(result.pages) > 0
|
||||
assert len(result.pages[0].links) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
os.environ.get("LLAMA_CLOUD_API_KEY", "") == "",
|
||||
reason="LLAMA_CLOUD_API_KEY not set",
|
||||
)
|
||||
async def test_parse_structured_output(file_path: str):
|
||||
parser = LlamaParse(
|
||||
structured_output=True,
|
||||
structured_output_json_schema_name="imFeelingLucky",
|
||||
)
|
||||
result = await parser.aparse(file_path)
|
||||
assert isinstance(result, JobResult)
|
||||
assert len(result.pages) > 0
|
||||
assert len(result.pages[0].structuredData) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
os.environ.get("LLAMA_CLOUD_API_KEY", "") == "",
|
||||
reason="LLAMA_CLOUD_API_KEY not set",
|
||||
)
|
||||
async def test_parse_charts(chart_file_path: str):
|
||||
parser = LlamaParse(
|
||||
extract_charts=True,
|
||||
)
|
||||
result = await parser.aparse(chart_file_path)
|
||||
assert isinstance(result, JobResult)
|
||||
assert len(result.pages) > 0
|
||||
assert len(result.pages[0].charts) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
os.environ.get("LLAMA_CLOUD_API_KEY", "") == "",
|
||||
reason="LLAMA_CLOUD_API_KEY not set",
|
||||
)
|
||||
async def test_parse_layout(file_path: str):
|
||||
parser = LlamaParse(
|
||||
extract_layout=True,
|
||||
)
|
||||
result = await parser.aparse(file_path)
|
||||
|
||||
assert isinstance(result, JobResult)
|
||||
assert len(result.pages) > 0
|
||||
assert len(result.pages[0].layout) > 0
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
os.environ.get("LLAMA_CLOUD_API_KEY", "") == "",
|
||||
reason="LLAMA_CLOUD_API_KEY not set",
|
||||
)
|
||||
def test_parse_multiple_files(file_path: str, chart_file_path: str):
|
||||
parser = LlamaParse()
|
||||
result = parser.parse([file_path, chart_file_path])
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 2
|
||||
assert isinstance(result[0], JobResult)
|
||||
assert isinstance(result[1], JobResult)
|
||||
assert result[0].file_name == file_path
|
||||
assert result[1].file_name == chart_file_path
|
||||
Binary file not shown.
Reference in New Issue
Block a user