mirror of
https://github.com/run-llama/llama_cloud_services.git
synced 2026-07-20 00:54:09 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a953225a15 | |||
| bf110ed2cf |
@@ -9,7 +9,6 @@ This repository contains the code for hand-written SDKs and clients for interact
|
||||
This includes:
|
||||
|
||||
- [LlamaParse](./parse.md) - A GenAI-native document parser that can parse complex document data for any downstream LLM use case (Agents, RAG, data processing, etc.).
|
||||
- [LlamaReport (beta/invite-only)](./report.md) - A prebuilt agentic report builder that can be used to build reports from a variety of data sources.
|
||||
- [LlamaExtract](./extract.md) - A prebuilt agentic data extractor that can be used to transform data into a structured JSON representation.
|
||||
- [LlamaCloud Index](./index.md) - A widely customizable and fully automated document ingestion pipeline that also serves retrieval purposes.
|
||||
|
||||
@@ -28,13 +27,11 @@ Then, you can use the services in your code:
|
||||
```python
|
||||
from llama_cloud_services import (
|
||||
LlamaParse,
|
||||
LlamaReport,
|
||||
LlamaExtract,
|
||||
LlamaCloudIndex,
|
||||
)
|
||||
|
||||
parser = LlamaParse(api_key="YOUR_API_KEY")
|
||||
report = LlamaReport(api_key="YOUR_API_KEY")
|
||||
extract = LlamaExtract(api_key="YOUR_API_KEY")
|
||||
index = LlamaCloudIndex(
|
||||
"my_first_index", project_name="default", api_key="YOUR_API_KEY"
|
||||
@@ -44,7 +41,6 @@ index = LlamaCloudIndex(
|
||||
See the quickstart guides for each service for more information:
|
||||
|
||||
- [LlamaParse](./parse.md)
|
||||
- [LlamaReport (beta/invite-only)](./report.md)
|
||||
- [LlamaExtract](./extract.md)
|
||||
- [LlamaCloud Index](./index.md)
|
||||
|
||||
@@ -57,13 +53,11 @@ You can also create your API key in the EU region [here](https://cloud.eu.llamai
|
||||
```python
|
||||
from llama_cloud_services import (
|
||||
LlamaParse,
|
||||
LlamaReport,
|
||||
LlamaExtract,
|
||||
EU_BASE_URL,
|
||||
)
|
||||
|
||||
parser = LlamaParse(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
report = LlamaReport(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
extract = LlamaExtract(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
index = LlamaCloudIndex(
|
||||
"my_first_index",
|
||||
|
||||
@@ -4,7 +4,6 @@ In this folder you will find several python notebooks that contain examples rega
|
||||
|
||||
- [LlamaParse](./parse/)
|
||||
- [LlamaExtract](./extract/)
|
||||
- [LlamaReport](./report/)
|
||||
- [LlamaCloudIndex](./index/)
|
||||
|
||||
Follow the instructions in each notebook to get started!
|
||||
|
||||
@@ -1,762 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Report Generation with LlamaReport\n",
|
||||
"\n",
|
||||
"In this notebook, we'll walk through the basic process of generating a report with LlamaReport, and highlight some of the key features of the library.\n",
|
||||
"\n",
|
||||
"TLDR:\n",
|
||||
"1. Download source data to use as knowledge base for the report\n",
|
||||
"2. Kick off report generation with a template\n",
|
||||
"3. Get the plan and review/accept/reject suggestions\n",
|
||||
"4. Get the final report\n",
|
||||
"5. Review/accept/reject suggestions to edit the final report\n",
|
||||
"6. Print the final report"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install llama-cloud-services"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Download Source Data\n",
|
||||
"\n",
|
||||
"Here, we download the `Attention is All You Need` paper as a PDF.\n",
|
||||
"\n",
|
||||
"LlamaReport currently supports up to 5 files as input, and essentially any file type that can be parsed by LlamaParse.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!wget \"https://arxiv.org/pdf/1706.03762.pdf\" -O \"./attention.pdf\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Kick off Report Generation\n",
|
||||
"\n",
|
||||
"Here, we kick off report generation with a template.\n",
|
||||
"\n",
|
||||
"The template can either be a string or a file path, but here we'll use a string.\n",
|
||||
"\n",
|
||||
"In our experiments, anything works as a template, but some general guidelines:\n",
|
||||
"\n",
|
||||
"- Use markdown formatting + instructions in each section to guide the report generation\n",
|
||||
"- If using an existing file as a template, provide extra instructions to guide the report generation\n",
|
||||
"\n",
|
||||
"**NOTE:** Since we are in a notebook, we will use async functions and `await` throughout. Synchronous methods that work without `await` are available by just removing the `a` from the method name and removing the `await` keyword."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from llama_cloud_services import LlamaReport\n",
|
||||
"\n",
|
||||
"llama_report = LlamaReport(\n",
|
||||
" api_key=\"llx-...\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"report_client = await llama_report.acreate_report(\n",
|
||||
" name=\"my_cool_report_on_attention\",\n",
|
||||
" # can pass in file paths or bytes\n",
|
||||
" input_files=[\"./attention.pdf\"],\n",
|
||||
" template_text=\"\"\"\\\n",
|
||||
"# [Some title]\\n\\n\n",
|
||||
"## TLDR\\n\n",
|
||||
"A quick summary of the paper.\\n\\n\n",
|
||||
"## Details\\n\n",
|
||||
"More details about the paper, possibly more than one section here.\\n\n",
|
||||
"\"\"\",\n",
|
||||
" # optional additional instructions for the report generation\n",
|
||||
" # template_instructions=None,\n",
|
||||
" # optional file path to an existing template instead of template_text\n",
|
||||
" # template_file=None,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The returned `ReportClient` object is used to interact with the report generation process for this specific report."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Report(id=0a394b33-1a3e-463c-b5cb-7ff8ab827d0a, name=my_cool_report_on_attention)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(report_client)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Get the plan\n",
|
||||
"\n",
|
||||
"The first phases of report generation involve ingesting the source data and generating a plan.\n",
|
||||
"\n",
|
||||
"The plan is a list of instructions for the report generation, and can be reviewed/accepted/rejected by the user.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"plan = await report_client.await_for_plan(\n",
|
||||
" timeout=10000,\n",
|
||||
" poll_interval=10,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"# {title}\n",
|
||||
"[ReportQuery(field='title', prompt='Generate a clear and concise title for this paper about the Transformer model and attention mechanisms', context='The paper discusses the Transformer architecture for sequence transduction using attention mechanisms, focusing on machine translation applications')]\n",
|
||||
"==================\n",
|
||||
"## TLDR\n",
|
||||
"\n",
|
||||
"{tldr_content}\n",
|
||||
"[ReportQuery(field='tldr_content', prompt='Write a brief, clear summary of the key points about the Transformer model', context='Focus on the main innovations: attention mechanisms, efficiency improvements, and state-of-the-art results in machine translation')]\n",
|
||||
"==================\n",
|
||||
"## Details\n",
|
||||
"\n",
|
||||
"{details_content}\n",
|
||||
"[ReportQuery(field='details_content', prompt='Provide detailed information about the Transformer model architecture and its applications', context='Include information about:\\n- The attention mechanism implementation\\n- Advantages over recurrent and convolutional models\\n- Performance in machine translation tasks\\n- Training efficiency improvements')]\n",
|
||||
"==================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for plan_block in plan.blocks:\n",
|
||||
" print(plan_block.block.template)\n",
|
||||
" print(plan_block.queries)\n",
|
||||
" print(\"==================\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"With the plan, we can either use it to kick off generation of the final report, or we can edit the plan and adjust it as needed.\n",
|
||||
"\n",
|
||||
"While we could manually edit the objects here and use `await report_client.aupdate_plan(action=\"edit\", updated_plan=plan)`, we can also use `LlamaReport` to agentically edit the plan."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"suggestions = await report_client.asuggest_edits(\n",
|
||||
" \"Can you split the details section into two sections?\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Justification for change: \n",
|
||||
"I'll help you break down the details section into two distinct parts - one focusing on the architecture and another on the practical applications and performance. This will make the content more organized and easier to follow. The original block at index 2 will be replaced with these two new sections.\n",
|
||||
"\n",
|
||||
"Proposed changes:\n",
|
||||
"\n",
|
||||
"## Architecture Details\n",
|
||||
"\n",
|
||||
"{architecture_content}\n",
|
||||
"\n",
|
||||
"[ReportQuery(field='architecture_content', prompt='Describe the technical details of the Transformer model architecture', context='Focus on:\\n- Core components of the Transformer architecture\\n- Self-attention mechanism implementation\\n- Multi-head attention details\\n- Position encoding approach\\n- Feed-forward network structure')]\n",
|
||||
"==================\n",
|
||||
"\n",
|
||||
"## Performance and Applications\n",
|
||||
"\n",
|
||||
"{applications_content}\n",
|
||||
"\n",
|
||||
"[ReportQuery(field='applications_content', prompt='Explain the practical applications and performance advantages of the Transformer model', context='Cover:\\n- Comparison with RNN and CNN models\\n- Machine translation results and benchmarks\\n- Training efficiency improvements\\n- Real-world applications and use cases\\n- Scalability benefits')]\n",
|
||||
"==================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for suggestion in suggestions:\n",
|
||||
" print(\"Justification for change:\", suggestion.justification)\n",
|
||||
" print(\"Proposed changes:\")\n",
|
||||
" for plan_block in suggestion.blocks:\n",
|
||||
" print(plan_block.block.template)\n",
|
||||
" print(plan_block.queries)\n",
|
||||
" print(\"==================\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This looks pretty good! We can also use the client to automatically accept and apply, or reject, these suggestions.\n",
|
||||
"\n",
|
||||
"This will (locally) keep track of the history of changes, so that future suggestions can be based on the previous changes."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for suggestion in suggestions:\n",
|
||||
" await report_client.aaccept_edit(suggestion)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"What effect did that have on the tracked local history? Let's see!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[EditAction(block_idx=2, old_content='## Details\\n\\n{details_content}\\n\\nField: details_content, Prompt: Provide detailed information about the Transformer model architecture and its applications, Context: Include information about:\\n- The attention mechanism implementation\\n- Advantages over recurrent and convolutional models\\n- Performance in machine translation tasks\\n- Training efficiency improvements\\nDepends on: none', new_content='\\n## Architecture Details\\n\\n{architecture_content}\\n\\n\\nField: architecture_content, Prompt: Describe the technical details of the Transformer model architecture, Context: Focus on:\\n- Core components of the Transformer architecture\\n- Self-attention mechanism implementation\\n- Multi-head attention details\\n- Position encoding approach\\n- Feed-forward network structure\\nDepends on: none', action='approved', timestamp=datetime.datetime(2025, 2, 4, 20, 59, 55, 773558)),\n",
|
||||
" EditAction(block_idx=3, old_content='[No old content]', new_content='\\n## Performance and Applications\\n\\n{applications_content}\\n\\n\\nField: applications_content, Prompt: Explain the practical applications and performance advantages of the Transformer model, Context: Cover:\\n- Comparison with RNN and CNN models\\n- Machine translation results and benchmarks\\n- Training efficiency improvements\\n- Real-world applications and use cases\\n- Scalability benefits\\nDepends on: previous', action='approved', timestamp=datetime.datetime(2025, 2, 4, 20, 59, 55, 773687))]"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"report_client.edit_history"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[Message(role=<MessageRole.USER: 'user'>, content='Can you split the details section into two sections?', timestamp=datetime.datetime(2025, 2, 4, 20, 59, 47, 754848)),\n",
|
||||
" Message(role=<MessageRole.ASSISTANT: 'assistant'>, content=\"\\nI'll help you break down the details section into two distinct parts - one focusing on the architecture and another on the practical applications and performance. This will make the content more organized and easier to follow. The original block at index 2 will be replaced with these two new sections.\\n\", timestamp=datetime.datetime(2025, 2, 4, 20, 59, 55, 482070))]"
|
||||
]
|
||||
},
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"report_client.chat_history"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"These two items are used to provide context for future suggestions! You can always clear this, or provide your own history."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# report_client.suggest_edits(\"....\", chat_history=[{\"role\": \"user\", \"content\": \"...\"}, ...])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Get the final report\n",
|
||||
"\n",
|
||||
"Now that we have a plan, we can kick off generation of the final report."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# kicks off report generation\n",
|
||||
"await report_client.aupdate_plan(action=\"approve\")\n",
|
||||
"\n",
|
||||
"# waits for report generation to complete\n",
|
||||
"report = await report_client.await_completion(\n",
|
||||
" timeout=10000,\n",
|
||||
" poll_interval=10,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"# Attention Is All You Need: A Pure Attention-Based Architecture for Neural Machine Translation\n",
|
||||
"\n",
|
||||
"## TLDR\n",
|
||||
"\n",
|
||||
"The Transformer introduced a revolutionary architecture that relies entirely on attention mechanisms, eliminating the need for recurrence or convolution in sequence processing. Its key innovations include multi-head self-attention for parallel processing of input sequences, scaled dot-product attention for efficient computation, and positional encodings for sequence order awareness. The model achieved breakthrough results in machine translation (28.4 BLEU on English-to-German, 41.8 BLEU on English-to-French) while requiring significantly less training time than previous approaches, training in 3.5 days on 8 GPUs. This architecture demonstrated that attention mechanisms alone are sufficient for state-of-the-art sequence modeling, setting a new direction for natural language processing.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Architecture Details\n",
|
||||
"\n",
|
||||
"The Transformer architecture represents a groundbreaking approach to sequence processing, built entirely on attention mechanisms without recurrence or convolution. Here are its key technical details:\n",
|
||||
"\n",
|
||||
"Core Components:\n",
|
||||
"- Encoder-decoder architecture with stacked self-attention and point-wise feed-forward layers\n",
|
||||
"- Each layer contains two main sub-layers: multi-head self-attention mechanism and position-wise feed-forward network\n",
|
||||
"- Layer normalization and residual connections between sub-layers\n",
|
||||
"- No recurrent or convolutional elements, enabling parallel processing\n",
|
||||
"\n",
|
||||
"Self-Attention Mechanism:\n",
|
||||
"- Processes relationships between all positions in a sequence simultaneously\n",
|
||||
"- Computes attention weights using queries, keys, and values derived from input representations\n",
|
||||
"- Implements scaled dot-product attention to prevent gradient issues with large input dimensions\n",
|
||||
"- Allows direct modeling of dependencies regardless of positional distance\n",
|
||||
"- Uses masking in decoder to prevent leftward information flow and maintain auto-regressive property\n",
|
||||
"\n",
|
||||
"Multi-Head Attention:\n",
|
||||
"- Employs multiple attention heads operating in parallel\n",
|
||||
"- Each head processes information in different representation subspaces\n",
|
||||
"- Three types of attention applications:\n",
|
||||
" 1. Encoder self-attention (all positions attend to each other)\n",
|
||||
" 2. Decoder self-attention (each position attends to previous positions)\n",
|
||||
" 3. Encoder-decoder attention (decoder queries attend to encoder outputs)\n",
|
||||
"- Counteracts reduced resolution from attention averaging through parallel processing\n",
|
||||
"\n",
|
||||
"Position-wise Feed-Forward Network:\n",
|
||||
"- Applied identically to each position separately\n",
|
||||
"- Consists of two linear transformations with ReLU activation\n",
|
||||
"- Structure: FFN(x) = max(0, xW1 + b1)W2 + b2\n",
|
||||
"- Input and output dimensionality: dmodel = 512\n",
|
||||
"- Inner-layer dimensionality: dff = 2048\n",
|
||||
"- Parameters vary between layers but remain constant across positions\n",
|
||||
"\n",
|
||||
"Position Encoding:\n",
|
||||
"- Adds positional information to input embeddings\n",
|
||||
"- Enables the model to consider sequential order without recurrence\n",
|
||||
"- Implements sinusoidal position encodings to allow model to attend to relative positions\n",
|
||||
"- Maintains constant number of operations between any two positions, unlike convolutional approaches\n",
|
||||
"- Allows effective modeling of both local and long-range dependencies\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Performance and Applications\n",
|
||||
"\n",
|
||||
"The Transformer model demonstrates significant performance advantages and practical applications across multiple domains:\n",
|
||||
"\n",
|
||||
"Performance Advantages over RNN/CNN Models:\n",
|
||||
"- Eliminates sequential computation constraints present in RNNs, enabling superior parallelization\n",
|
||||
"- Reduces operations needed for relating distant positions to a constant number, compared to linear/logarithmic scaling in CNNs\n",
|
||||
"- Processes all input and output positions simultaneously through self-attention mechanisms\n",
|
||||
"- Achieves state-of-the-art results while requiring significantly less computational resources\n",
|
||||
"\n",
|
||||
"Machine Translation Benchmarks:\n",
|
||||
"- WMT 2014 English-to-German: 28.4 BLEU score, exceeding previous best results by over 2 BLEU points\n",
|
||||
"- WMT 2014 English-to-French: 41.8 BLEU score (single-model state-of-the-art)\n",
|
||||
"- Surpasses performance of existing model ensembles in translation tasks\n",
|
||||
"\n",
|
||||
"Training Efficiency:\n",
|
||||
"- Requires only 3.5 days of training on eight GPUs for state-of-the-art performance\n",
|
||||
"- Achieves superior results at \"a small fraction of the training costs\" compared to previous models\n",
|
||||
"- Enables significantly faster training through parallel processing of input/output sequences\n",
|
||||
"- Can reach production-quality performance in as little as twelve hours on modern GPU hardware\n",
|
||||
"\n",
|
||||
"Real-world Applications:\n",
|
||||
"- Machine translation systems\n",
|
||||
"- Natural language understanding tasks\n",
|
||||
"- Reading comprehension\n",
|
||||
"- Abstractive summarization\n",
|
||||
"- Text entailment analysis\n",
|
||||
"- Constituency parsing (achieving 92.7 F1 score in semi-supervised settings)\n",
|
||||
"- Adaptable to both large and limited training data scenarios\n",
|
||||
"\n",
|
||||
"Scalability Benefits:\n",
|
||||
"- Highly parallelizable architecture enables efficient scaling across multiple GPUs\n",
|
||||
"- Constant computational complexity for relating any input/output positions\n",
|
||||
"- Effective handling of long-range dependencies in sequences\n",
|
||||
"- Maintains performance quality while scaling to larger datasets and model sizes\n",
|
||||
"- Generalizes well across different tasks and domains without architectural changes\n",
|
||||
"- Supports efficient inference and deployment in production environments\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"report_text = \"\\n\\n\".join([block.template for block in report.blocks])\n",
|
||||
"print(report_text)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Edit the final report\n",
|
||||
"\n",
|
||||
"Now that we have a report, we can edit it.\n",
|
||||
"\n",
|
||||
"We can use the `asuggest_edits` method to get suggestions for edits, and then use the `aaccept_edit`/`areject_edit` methods to apply them.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Justification for change: \n",
|
||||
"I'd suggest changing \"TLDR\" to \"Executive Summary\" which is more appropriate for a professional or academic report. This term is widely used in formal documents and better reflects the nature of this concise overview section while maintaining the same function of providing a quick summary of the key points.\n",
|
||||
"\n",
|
||||
"Proposed changes:\n",
|
||||
"## Executive Summary\n",
|
||||
"\n",
|
||||
"The Transformer introduced a revolutionary architecture that relies entirely on attention mechanisms, eliminating the need for recurrence or convolution in sequence processing. Its key innovations include multi-head self-attention for parallel processing of input sequences, scaled dot-product attention for efficient computation, and positional encodings for sequence order awareness. The model achieved breakthrough results in machine translation (28.4 BLEU on English-to-German, 41.8 BLEU on English-to-French) while requiring significantly less training time than previous approaches, training in 3.5 days on 8 GPUs. This architecture demonstrated that attention mechanisms alone are sufficient for state-of-the-art sequence modeling, setting a new direction for natural language processing.\n",
|
||||
"==================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"suggestions = await report_client.asuggest_edits(\n",
|
||||
" \"Can you change the TLDR header to something more professional?\"\n",
|
||||
")\n",
|
||||
"for suggestion in suggestions:\n",
|
||||
" print(\"Justification for change:\", suggestion.justification)\n",
|
||||
" print(\"Proposed changes:\")\n",
|
||||
" for block in suggestion.blocks:\n",
|
||||
" print(block.template)\n",
|
||||
" print(\"==================\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Changing to \"Executive Summary\" sounds reasonable, lets accept that!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for suggestion in suggestions:\n",
|
||||
" await report_client.aaccept_edit(suggestion)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 7. Print the final report\n",
|
||||
"\n",
|
||||
"Now that we have a report, we can print it."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"# Attention Is All You Need: A Pure Attention-Based Architecture for Neural Machine Translation\n",
|
||||
"\n",
|
||||
"## Executive Summary\n",
|
||||
"\n",
|
||||
"The Transformer introduced a revolutionary architecture that relies entirely on attention mechanisms, eliminating the need for recurrence or convolution in sequence processing. Its key innovations include multi-head self-attention for parallel processing of input sequences, scaled dot-product attention for efficient computation, and positional encodings for sequence order awareness. The model achieved breakthrough results in machine translation (28.4 BLEU on English-to-German, 41.8 BLEU on English-to-French) while requiring significantly less training time than previous approaches, training in 3.5 days on 8 GPUs. This architecture demonstrated that attention mechanisms alone are sufficient for state-of-the-art sequence modeling, setting a new direction for natural language processing.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Architecture Details\n",
|
||||
"\n",
|
||||
"The Transformer architecture represents a groundbreaking approach to sequence processing, built entirely on attention mechanisms without recurrence or convolution. Here are its key technical details:\n",
|
||||
"\n",
|
||||
"Core Components:\n",
|
||||
"- Encoder-decoder architecture with stacked self-attention and point-wise feed-forward layers\n",
|
||||
"- Each layer contains two main sub-layers: multi-head self-attention mechanism and position-wise feed-forward network\n",
|
||||
"- Layer normalization and residual connections between sub-layers\n",
|
||||
"- No recurrent or convolutional elements, enabling parallel processing\n",
|
||||
"\n",
|
||||
"Self-Attention Mechanism:\n",
|
||||
"- Processes relationships between all positions in a sequence simultaneously\n",
|
||||
"- Computes attention weights using queries, keys, and values derived from input representations\n",
|
||||
"- Implements scaled dot-product attention to prevent gradient issues with large input dimensions\n",
|
||||
"- Allows direct modeling of dependencies regardless of positional distance\n",
|
||||
"- Uses masking in decoder to prevent leftward information flow and maintain auto-regressive property\n",
|
||||
"\n",
|
||||
"Multi-Head Attention:\n",
|
||||
"- Employs multiple attention heads operating in parallel\n",
|
||||
"- Each head processes information in different representation subspaces\n",
|
||||
"- Three types of attention applications:\n",
|
||||
" 1. Encoder self-attention (all positions attend to each other)\n",
|
||||
" 2. Decoder self-attention (each position attends to previous positions)\n",
|
||||
" 3. Encoder-decoder attention (decoder queries attend to encoder outputs)\n",
|
||||
"- Counteracts reduced resolution from attention averaging through parallel processing\n",
|
||||
"\n",
|
||||
"Position-wise Feed-Forward Network:\n",
|
||||
"- Applied identically to each position separately\n",
|
||||
"- Consists of two linear transformations with ReLU activation\n",
|
||||
"- Structure: FFN(x) = max(0, xW1 + b1)W2 + b2\n",
|
||||
"- Input and output dimensionality: dmodel = 512\n",
|
||||
"- Inner-layer dimensionality: dff = 2048\n",
|
||||
"- Parameters vary between layers but remain constant across positions\n",
|
||||
"\n",
|
||||
"Position Encoding:\n",
|
||||
"- Adds positional information to input embeddings\n",
|
||||
"- Enables the model to consider sequential order without recurrence\n",
|
||||
"- Implements sinusoidal position encodings to allow model to attend to relative positions\n",
|
||||
"- Maintains constant number of operations between any two positions, unlike convolutional approaches\n",
|
||||
"- Allows effective modeling of both local and long-range dependencies\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"## Performance and Applications\n",
|
||||
"\n",
|
||||
"The Transformer model demonstrates significant performance advantages and practical applications across multiple domains:\n",
|
||||
"\n",
|
||||
"Performance Advantages over RNN/CNN Models:\n",
|
||||
"- Eliminates sequential computation constraints present in RNNs, enabling superior parallelization\n",
|
||||
"- Reduces operations needed for relating distant positions to a constant number, compared to linear/logarithmic scaling in CNNs\n",
|
||||
"- Processes all input and output positions simultaneously through self-attention mechanisms\n",
|
||||
"- Achieves state-of-the-art results while requiring significantly less computational resources\n",
|
||||
"\n",
|
||||
"Machine Translation Benchmarks:\n",
|
||||
"- WMT 2014 English-to-German: 28.4 BLEU score, exceeding previous best results by over 2 BLEU points\n",
|
||||
"- WMT 2014 English-to-French: 41.8 BLEU score (single-model state-of-the-art)\n",
|
||||
"- Surpasses performance of existing model ensembles in translation tasks\n",
|
||||
"\n",
|
||||
"Training Efficiency:\n",
|
||||
"- Requires only 3.5 days of training on eight GPUs for state-of-the-art performance\n",
|
||||
"- Achieves superior results at \"a small fraction of the training costs\" compared to previous models\n",
|
||||
"- Enables significantly faster training through parallel processing of input/output sequences\n",
|
||||
"- Can reach production-quality performance in as little as twelve hours on modern GPU hardware\n",
|
||||
"\n",
|
||||
"Real-world Applications:\n",
|
||||
"- Machine translation systems\n",
|
||||
"- Natural language understanding tasks\n",
|
||||
"- Reading comprehension\n",
|
||||
"- Abstractive summarization\n",
|
||||
"- Text entailment analysis\n",
|
||||
"- Constituency parsing (achieving 92.7 F1 score in semi-supervised settings)\n",
|
||||
"- Adaptable to both large and limited training data scenarios\n",
|
||||
"\n",
|
||||
"Scalability Benefits:\n",
|
||||
"- Highly parallelizable architecture enables efficient scaling across multiple GPUs\n",
|
||||
"- Constant computational complexity for relating any input/output positions\n",
|
||||
"- Effective handling of long-range dependencies in sequences\n",
|
||||
"- Maintains performance quality while scaling to larger datasets and model sizes\n",
|
||||
"- Generalizes well across different tasks and domains without architectural changes\n",
|
||||
"- Supports efficient inference and deployment in production environments\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"report_response = await report_client.aget()\n",
|
||||
"report_text = \"\\n\\n\".join([block.template for block in report_response.report.blocks])\n",
|
||||
"print(report_text)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can also see the sources for each block!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"0.99687636\n",
|
||||
"# Abstract\n",
|
||||
"\n",
|
||||
"The dominant sequence transduction models are based on complex recurrent or convolutiona\n",
|
||||
"==================\n",
|
||||
"0.99591404\n",
|
||||
"# 2 Background\n",
|
||||
"\n",
|
||||
"The goal of reducing sequential computation also forms the foundation of the Extende\n",
|
||||
"==================\n",
|
||||
"0.9951325\n",
|
||||
"# 1 Introduction\n",
|
||||
"\n",
|
||||
"Recurrent neural networks, long short-term memory [13] and gated recurrent [7] neu\n",
|
||||
"==================\n",
|
||||
"0.99442345\n",
|
||||
"# 7 Conclusion\n",
|
||||
"\n",
|
||||
"In this work, we presented the Transformer, the first sequence transduction model ba\n",
|
||||
"==================\n",
|
||||
"0.9967649\n",
|
||||
"# 3.2.3 Applications of Attention in our Model\n",
|
||||
"\n",
|
||||
"The Transformer uses multi-head attention in three d\n",
|
||||
"==================\n",
|
||||
"0.99533635\n",
|
||||
"# 2 Background\n",
|
||||
"\n",
|
||||
"The goal of reducing sequential computation also forms the foundation of the Extende\n",
|
||||
"==================\n",
|
||||
"0.9935868\n",
|
||||
"# Abstract\n",
|
||||
"\n",
|
||||
"The dominant sequence transduction models are based on complex recurrent or convolutiona\n",
|
||||
"==================\n",
|
||||
"0.98780584\n",
|
||||
"# Outputs\n",
|
||||
"\n",
|
||||
"(shifted right)\n",
|
||||
"\n",
|
||||
"Figure 1: The Transformer - model architecture.\n",
|
||||
"\n",
|
||||
"The Transformer follows\n",
|
||||
"==================\n",
|
||||
"0.9205043\n",
|
||||
"# 3.3 Position-wise Feed-Forward Networks\n",
|
||||
"\n",
|
||||
"In addition to attention sub-layers, each of the layers i\n",
|
||||
"==================\n",
|
||||
"0.79581684\n",
|
||||
"# 1 Introduction\n",
|
||||
"\n",
|
||||
"Recurrent neural networks, long short-term memory [13] and gated recurrent [7] neu\n",
|
||||
"==================\n",
|
||||
"0.9946774\n",
|
||||
"# Abstract\n",
|
||||
"\n",
|
||||
"The dominant sequence transduction models are based on complex recurrent or convolutiona\n",
|
||||
"==================\n",
|
||||
"0.97079873\n",
|
||||
"# 7 Conclusion\n",
|
||||
"\n",
|
||||
"In this work, we presented the Transformer, the first sequence transduction model ba\n",
|
||||
"==================\n",
|
||||
"0.9535353\n",
|
||||
"# 6.3 English Constituency Parsing\n",
|
||||
"\n",
|
||||
"To evaluate if the Transformer can generalize to other tasks we \n",
|
||||
"==================\n",
|
||||
"0.9514138\n",
|
||||
"# 2 Background\n",
|
||||
"\n",
|
||||
"The goal of reducing sequential computation also forms the foundation of the Extende\n",
|
||||
"==================\n",
|
||||
"0.9790758\n",
|
||||
"# 1 Introduction\n",
|
||||
"\n",
|
||||
"Recurrent neural networks, long short-term memory [13] and gated recurrent [7] neu\n",
|
||||
"==================\n",
|
||||
"0.92262185\n",
|
||||
"# Outputs\n",
|
||||
"\n",
|
||||
"(shifted right)\n",
|
||||
"\n",
|
||||
"Figure 1: The Transformer - model architecture.\n",
|
||||
"\n",
|
||||
"The Transformer follows\n",
|
||||
"==================\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for block in report_response.report.blocks:\n",
|
||||
" # Each block has a list of sources, which are the nodes that were used to generate the block\n",
|
||||
" for source in block.sources:\n",
|
||||
" print(source.score)\n",
|
||||
" print(source.node.text[:100])\n",
|
||||
" print(\"==================\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "llama-parse-aNC435Vv-py3.10",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
+1
-7
@@ -9,7 +9,6 @@ This repository contains the code for hand-written SDKs and clients for interact
|
||||
This includes:
|
||||
|
||||
- [LlamaParse](../parse.md) - A GenAI-native document parser that can parse complex document data for any downstream LLM use case (Agents, RAG, data processing, etc.).
|
||||
- [LlamaReport (beta/invite-only)](../report.md) - A prebuilt agentic report builder that can be used to build reports from a variety of data sources.
|
||||
- [LlamaExtract](../extract.md) - A prebuilt agentic data extractor that can be used to transform data into a structured JSON representation.
|
||||
- [LlamaCloud Index](../index.md) - A widely customizable and fully automated document ingestion pipeline that also serves retrieval purposes.
|
||||
|
||||
@@ -28,14 +27,12 @@ Then, you can use the services in your code:
|
||||
```python
|
||||
from llama_cloud_services import (
|
||||
LlamaParse,
|
||||
LlamaReport,
|
||||
LlamaExtract,
|
||||
LlamaCloudIndex,
|
||||
)
|
||||
from llama_cloud_services import LlamaParse, LlamaReport, LlamaExtract
|
||||
from llama_cloud_services import LlamaParse, LlamaExtract
|
||||
|
||||
parser = LlamaParse(api_key="YOUR_API_KEY")
|
||||
report = LlamaReport(api_key="YOUR_API_KEY")
|
||||
extract = LlamaExtract(api_key="YOUR_API_KEY")
|
||||
index = LlamaCloudIndex(
|
||||
"my_first_index", project_name="default", api_key="YOUR_API_KEY"
|
||||
@@ -45,7 +42,6 @@ index = LlamaCloudIndex(
|
||||
See the quickstart guides for each service for more information:
|
||||
|
||||
- [LlamaParse](../parse.md)
|
||||
- [LlamaReport (beta/invite-only)](../report.md)
|
||||
- [LlamaExtract](../extract.md)
|
||||
- [LlamaCloud Index](../index.md)
|
||||
|
||||
@@ -58,13 +54,11 @@ You can also create your API key in the EU region [here](https://cloud.eu.llamai
|
||||
```python
|
||||
from llama_cloud_services import (
|
||||
LlamaParse,
|
||||
LlamaReport,
|
||||
LlamaExtract,
|
||||
EU_BASE_URL,
|
||||
)
|
||||
|
||||
parser = LlamaParse(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
report = LlamaReport(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
extract = LlamaExtract(api_key="YOUR_API_KEY", base_url=EU_BASE_URL)
|
||||
index = LlamaCloudIndex(
|
||||
"my_first_index",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from llama_cloud_services.parse import LlamaParse
|
||||
from llama_cloud_services.report import ReportClient, LlamaReport
|
||||
from llama_cloud_services.extract import LlamaExtract, ExtractionAgent, SourceText
|
||||
from llama_cloud_services.constants import EU_BASE_URL
|
||||
from llama_cloud_services.index import (
|
||||
@@ -10,8 +9,6 @@ from llama_cloud_services.index import (
|
||||
|
||||
__all__ = [
|
||||
"LlamaParse",
|
||||
"ReportClient",
|
||||
"LlamaReport",
|
||||
"LlamaExtract",
|
||||
"ExtractionAgent",
|
||||
"SourceText",
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from llama_cloud_services.report.report import ReportClient
|
||||
from llama_cloud_services.report.base import LlamaReport
|
||||
|
||||
__all__ = ["ReportClient", "LlamaReport"]
|
||||
@@ -1,269 +0,0 @@
|
||||
import asyncio
|
||||
import httpx
|
||||
import os
|
||||
import io
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Optional, List, Union, Any, Coroutine, TypeVar
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from llama_cloud.types import ReportMetadata
|
||||
from llama_cloud_services.report.report import ReportClient
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class LlamaReport:
|
||||
"""Client for managing reports and general report operations."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
project_id: Optional[str] = None,
|
||||
organization_id: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
timeout: Optional[int] = None,
|
||||
async_httpx_client: Optional[httpx.AsyncClient] = None,
|
||||
):
|
||||
self.api_key = api_key or os.getenv("LLAMA_CLOUD_API_KEY", None)
|
||||
if not self.api_key:
|
||||
raise ValueError("No API key provided.")
|
||||
|
||||
self.base_url = base_url or os.getenv(
|
||||
"LLAMA_CLOUD_BASE_URL", "https://api.cloud.llamaindex.ai"
|
||||
)
|
||||
self.timeout = timeout or 60
|
||||
|
||||
# Initialize HTTP clients
|
||||
self._aclient = async_httpx_client or httpx.AsyncClient(timeout=self.timeout)
|
||||
|
||||
# Set auth headers
|
||||
self.headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
}
|
||||
|
||||
self.organization_id = organization_id
|
||||
self.project_id = project_id
|
||||
self._client_params = {
|
||||
"timeout": self._aclient.timeout,
|
||||
"headers": self._aclient.headers,
|
||||
"base_url": self._aclient.base_url,
|
||||
"auth": self._aclient.auth,
|
||||
"event_hooks": self._aclient.event_hooks,
|
||||
"cookies": self._aclient.cookies,
|
||||
"max_redirects": self._aclient.max_redirects,
|
||||
"params": self._aclient.params,
|
||||
"trust_env": self._aclient.trust_env,
|
||||
}
|
||||
self._thread_pool = ThreadPoolExecutor(
|
||||
max_workers=min(10, (os.cpu_count() or 1) + 4)
|
||||
)
|
||||
|
||||
@property
|
||||
def aclient(self) -> httpx.AsyncClient:
|
||||
if self._aclient is None:
|
||||
self._aclient = httpx.AsyncClient(**self._client_params)
|
||||
return self._aclient
|
||||
|
||||
def _run_sync(self, coro: Coroutine[Any, Any, T]) -> T:
|
||||
"""Run coroutine in a separate thread to avoid event loop issues"""
|
||||
|
||||
# force a new client for this thread/event loop
|
||||
original_client = self._aclient
|
||||
self._aclient = None
|
||||
|
||||
def run_coro() -> T:
|
||||
async def wrapped_coro() -> T:
|
||||
return await coro
|
||||
|
||||
return asyncio.run(wrapped_coro())
|
||||
|
||||
result = self._thread_pool.submit(run_coro).result()
|
||||
|
||||
# restore the original client
|
||||
self._aclient = original_client
|
||||
|
||||
return result
|
||||
|
||||
async def _get_default_project(self) -> str:
|
||||
response = await self.aclient.get(
|
||||
urljoin(str(self.base_url), "/api/v1/projects"), headers=self.headers
|
||||
)
|
||||
response.raise_for_status()
|
||||
projects = response.json()
|
||||
default_project = [p for p in projects if p.get("is_default")]
|
||||
return default_project[0]["id"]
|
||||
|
||||
async def _build_url(
|
||||
self, endpoint: str, extra_params: Optional[List[str]] = None
|
||||
) -> str:
|
||||
"""Helper method to build URLs with common query parameters."""
|
||||
url = urljoin(str(self.base_url), endpoint)
|
||||
|
||||
if not self.project_id:
|
||||
self.project_id = await self._get_default_project()
|
||||
|
||||
query_params = []
|
||||
if self.organization_id:
|
||||
query_params.append(f"organization_id={self.organization_id}")
|
||||
if self.project_id:
|
||||
query_params.append(f"project_id={self.project_id}")
|
||||
if extra_params:
|
||||
query_params.extend([p for p in extra_params if p is not None])
|
||||
|
||||
if query_params:
|
||||
url += "?" + "&".join(query_params)
|
||||
|
||||
return url
|
||||
|
||||
async def acreate_report(
|
||||
self,
|
||||
name: str,
|
||||
template_instructions: Optional[str] = None,
|
||||
template_text: Optional[str] = None,
|
||||
template_file: Optional[Union[str, tuple[str, bytes]]] = None,
|
||||
input_files: Optional[List[Union[str, tuple[str, bytes]]]] = None,
|
||||
existing_retriever_id: Optional[str] = None,
|
||||
) -> ReportClient:
|
||||
"""Create a new report asynchronously."""
|
||||
url = await self._build_url("/api/v1/reports/")
|
||||
open_files: List[io.BufferedReader] = []
|
||||
|
||||
data = {"name": name}
|
||||
if template_instructions:
|
||||
data["template_instructions"] = template_instructions
|
||||
if template_text:
|
||||
data["template_text"] = template_text
|
||||
if existing_retriever_id:
|
||||
data["existing_retriever_id"] = str(existing_retriever_id)
|
||||
|
||||
files: List[tuple[str, io.BufferedReader | bytes]] = []
|
||||
if template_file:
|
||||
if isinstance(template_file, str):
|
||||
open_files.append(open(template_file, "rb"))
|
||||
files.append(("template_file", open_files[-1]))
|
||||
else:
|
||||
files.append(("template_file", template_file[1]))
|
||||
|
||||
if input_files:
|
||||
for f in input_files:
|
||||
if isinstance(f, str):
|
||||
open_files.append(open(f, "rb"))
|
||||
files.append(("files", open_files[-1]))
|
||||
else:
|
||||
files.append(("files", f[1]))
|
||||
|
||||
response = await self.aclient.post(
|
||||
url, headers=self.headers, data=data, files=files
|
||||
)
|
||||
try:
|
||||
response.raise_for_status()
|
||||
report_id = response.json()["id"]
|
||||
return ReportClient(report_id, name, self)
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise ValueError(
|
||||
f"Failed to create report: {e.response.text}\nError Code: {e.response.status_code}"
|
||||
)
|
||||
finally:
|
||||
for open_file in open_files:
|
||||
open_file.close()
|
||||
|
||||
def create_report(
|
||||
self,
|
||||
name: str,
|
||||
template_instructions: Optional[str] = None,
|
||||
template_text: Optional[str] = None,
|
||||
template_file: Optional[Union[str, tuple[str, bytes]]] = None,
|
||||
input_files: Optional[List[Union[str, tuple[str, bytes]]]] = None,
|
||||
existing_retriever_id: Optional[str] = None,
|
||||
) -> ReportClient:
|
||||
"""Create a new report."""
|
||||
return self._run_sync(
|
||||
self.acreate_report(
|
||||
name=name,
|
||||
template_instructions=template_instructions,
|
||||
template_text=template_text,
|
||||
template_file=template_file,
|
||||
input_files=input_files,
|
||||
existing_retriever_id=existing_retriever_id,
|
||||
)
|
||||
)
|
||||
|
||||
async def alist_reports(
|
||||
self, state: Optional[str] = None, limit: int = 100, offset: int = 0
|
||||
) -> List[ReportClient]:
|
||||
"""List all reports asynchronously."""
|
||||
params = []
|
||||
if state:
|
||||
params.append(f"state={state}")
|
||||
if limit:
|
||||
params.append(f"limit={limit}")
|
||||
if offset:
|
||||
params.append(f"offset={offset}")
|
||||
|
||||
url = await self._build_url(
|
||||
"/api/v1/reports/list",
|
||||
extra_params=params,
|
||||
)
|
||||
|
||||
response = await self.aclient.get(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
return [
|
||||
ReportClient(r["report_id"], r["name"], self)
|
||||
for r in data["report_responses"]
|
||||
]
|
||||
|
||||
def list_reports(
|
||||
self, state: Optional[str] = None, limit: int = 100, offset: int = 0
|
||||
) -> List[ReportClient]:
|
||||
"""Synchronous wrapper for listing reports."""
|
||||
return self._run_sync(self.alist_reports(state, limit, offset))
|
||||
|
||||
async def aget_report(self, report_id: str) -> ReportClient:
|
||||
"""Get a Report instance for working with a specific report."""
|
||||
url = await self._build_url(f"/api/v1/reports/{report_id}")
|
||||
|
||||
response = await self.aclient.get(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
return ReportClient(data["report_id"], data["name"], self)
|
||||
|
||||
def get_report(self, report_id: str) -> ReportClient:
|
||||
"""Synchronous wrapper for getting a report."""
|
||||
return self._run_sync(self.aget_report(report_id))
|
||||
|
||||
async def aget_report_metadata(self, report_id: str) -> ReportMetadata:
|
||||
"""Get metadata for a specific report asynchronously.
|
||||
|
||||
Returns:
|
||||
dict containing:
|
||||
- id: Report ID
|
||||
- name: Report name
|
||||
- state: Current report state
|
||||
- report_metadata: Additional metadata
|
||||
- template_file: Name of template file if used
|
||||
- template_instructions: Template instructions if provided
|
||||
- input_files: List of input file names
|
||||
"""
|
||||
url = await self._build_url(f"/api/v1/reports/{report_id}/metadata")
|
||||
|
||||
response = await self.aclient.get(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
return ReportMetadata(**response.json())
|
||||
|
||||
def get_report_metadata(self, report_id: str) -> ReportMetadata:
|
||||
"""Synchronous wrapper for getting report metadata."""
|
||||
return self._run_sync(self.aget_report_metadata(report_id))
|
||||
|
||||
async def adelete_report(self, report_id: str) -> None:
|
||||
"""Delete a specific report asynchronously."""
|
||||
url = await self._build_url(f"/api/v1/reports/{report_id}")
|
||||
|
||||
response = await self.aclient.delete(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
|
||||
def delete_report(self, report_id: str) -> None:
|
||||
"""Synchronous wrapper for deleting a report."""
|
||||
return self._run_sync(self.adelete_report(report_id))
|
||||
@@ -1,527 +0,0 @@
|
||||
import asyncio
|
||||
import httpx
|
||||
import time
|
||||
from typing import Optional, List, Literal, Union, TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
from llama_cloud.types import (
|
||||
ReportEventItemEventData_Progress,
|
||||
ReportMetadata,
|
||||
EditSuggestion,
|
||||
ReportResponse,
|
||||
ReportPlan,
|
||||
ReportBlock,
|
||||
ReportPlanBlock,
|
||||
Report,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from llama_cloud_services.report.base import LlamaReport
|
||||
|
||||
|
||||
class MessageRole(str, Enum):
|
||||
USER = "user"
|
||||
ASSISTANT = "assistant"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
role: MessageRole
|
||||
content: str
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class EditAction:
|
||||
block_idx: int
|
||||
old_content: str
|
||||
new_content: Optional[str]
|
||||
action: Literal["approved", "rejected"]
|
||||
timestamp: datetime
|
||||
|
||||
|
||||
DEFAULT_POLL_INTERVAL = 5
|
||||
DEFAULT_TIMEOUT = 600
|
||||
|
||||
|
||||
class ReportClient:
|
||||
"""Client for operations on a specific report."""
|
||||
|
||||
def __init__(self, report_id: str, name: str, parent_client: "LlamaReport"):
|
||||
self.report_id = report_id
|
||||
self.name = name
|
||||
self._client = parent_client
|
||||
self._headers = parent_client.headers
|
||||
self._run_sync = parent_client._run_sync
|
||||
self._build_url = parent_client._build_url
|
||||
self.chat_history: List[Message] = []
|
||||
self.edit_history: List[EditAction] = []
|
||||
|
||||
@property
|
||||
def aclient(self) -> httpx.AsyncClient:
|
||||
return self._client.aclient
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Report(id={self.report_id}, name={self.name})"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Report(id={self.report_id}, name={self.name})"
|
||||
|
||||
def _get_block_content(self, block: Union[ReportBlock, ReportPlanBlock]) -> str:
|
||||
if isinstance(block, ReportBlock):
|
||||
return block.template
|
||||
elif isinstance(block, ReportPlanBlock):
|
||||
return block.block.template
|
||||
else:
|
||||
raise ValueError(f"Invalid block type: {type(block)}")
|
||||
|
||||
def _get_block_idx(self, block: Union[ReportBlock, ReportPlanBlock]) -> int:
|
||||
if isinstance(block, ReportBlock):
|
||||
return block.idx
|
||||
elif isinstance(block, ReportPlanBlock):
|
||||
return block.block.idx
|
||||
else:
|
||||
raise ValueError(f"Invalid block type: {type(block)}")
|
||||
|
||||
async def aget(self, version: Optional[int] = None) -> ReportResponse:
|
||||
"""Get this report's details asynchronously."""
|
||||
extra_params = []
|
||||
if version is not None:
|
||||
extra_params.append(f"version={version}")
|
||||
|
||||
url = await self._build_url(f"/api/v1/reports/{self.report_id}", extra_params)
|
||||
|
||||
response = await self.aclient.get(url, headers=self._headers)
|
||||
response.raise_for_status()
|
||||
return ReportResponse(**response.json())
|
||||
|
||||
def get(self, version: Optional[int] = None) -> ReportResponse:
|
||||
"""Synchronous wrapper for getting this report's details."""
|
||||
return self._run_sync(self.aget(version))
|
||||
|
||||
async def aupdate_report(self, updated_report: Report) -> ReportResponse:
|
||||
"""Update this report's content asynchronously."""
|
||||
url = await self._build_url(f"/api/v1/reports/{self.report_id}")
|
||||
response = await self.aclient.patch(
|
||||
url, headers=self._headers, json={"content": updated_report.dict()}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return ReportResponse(**response.json())
|
||||
|
||||
def update_report(self, updated_report: Report) -> ReportResponse:
|
||||
"""Synchronous wrapper for updating this report's content."""
|
||||
return self._run_sync(self.aupdate_report(updated_report))
|
||||
|
||||
async def aupdate_plan(
|
||||
self,
|
||||
action: Literal["approve", "reject", "edit"],
|
||||
updated_plan: Optional[ReportPlan] = None,
|
||||
) -> ReportResponse:
|
||||
"""Update this report's plan asynchronously."""
|
||||
if action == "edit" and not updated_plan:
|
||||
raise ValueError("updated_plan is required when action is 'edit'")
|
||||
|
||||
url = await self._build_url(
|
||||
f"/api/v1/reports/{self.report_id}/plan", [f"action={action}"]
|
||||
)
|
||||
|
||||
data = None
|
||||
if updated_plan is not None:
|
||||
plan_dict = updated_plan.dict()
|
||||
plan_dict.pop("generated_at", None)
|
||||
data = plan_dict
|
||||
|
||||
if updated_plan is None and action == "edit":
|
||||
raise ValueError("updated_plan is required when action is 'edit'")
|
||||
|
||||
response = await self.aclient.patch(url, headers=self._headers, json=data)
|
||||
response.raise_for_status()
|
||||
return ReportResponse(**response.json())
|
||||
|
||||
def update_plan(
|
||||
self,
|
||||
action: Literal["approve", "reject", "edit"],
|
||||
updated_plan: Optional[ReportPlan] = None,
|
||||
) -> ReportResponse:
|
||||
"""Synchronous wrapper for updating this report's plan."""
|
||||
return self._run_sync(self.aupdate_plan(action, updated_plan))
|
||||
|
||||
async def asuggest_edits(
|
||||
self,
|
||||
user_query: str,
|
||||
auto_history: bool = True,
|
||||
chat_history: Optional[List[dict]] = None,
|
||||
) -> List[EditSuggestion]:
|
||||
"""Get AI suggestions for edits to this report asynchronously.
|
||||
|
||||
Args:
|
||||
user_query: The user's request/question about what to edit
|
||||
auto_history: Whether to automatically add the user's message to the chat history
|
||||
chat_history:
|
||||
A list of chat messages to include in the chat history.
|
||||
The format being a list of dictionaries with "role" and "content" keys.
|
||||
"""
|
||||
# Add user message to history
|
||||
self.chat_history.append(
|
||||
Message(role=MessageRole.USER, content=user_query, timestamp=datetime.now())
|
||||
)
|
||||
|
||||
# Format chat history with edit summaries
|
||||
chat_history_dicts = []
|
||||
for msg in self.chat_history[:-1]: # Exclude current message
|
||||
content = msg.content
|
||||
if msg.role == MessageRole.USER:
|
||||
# Add edit summary for user messages
|
||||
edit_summary = self._get_edit_summary_after_message(msg.timestamp)
|
||||
if edit_summary:
|
||||
content = f"{content}\n\nActions taken:\n{edit_summary}"
|
||||
|
||||
chat_history_dicts.append({"role": msg.role.value, "content": content})
|
||||
|
||||
# decide whether to include chat history or not
|
||||
if chat_history:
|
||||
chat_history_dicts = chat_history
|
||||
elif auto_history:
|
||||
chat_history_dicts = chat_history_dicts
|
||||
else:
|
||||
chat_history_dicts = []
|
||||
|
||||
# Make the API call
|
||||
url = await self._build_url(f"/api/v1/reports/{self.report_id}/suggest_edits")
|
||||
data = {"user_query": user_query, "chat_history": chat_history_dicts}
|
||||
|
||||
response = await self.aclient.post(url, headers=self._headers, json=data)
|
||||
response.raise_for_status()
|
||||
suggestions = response.json()
|
||||
suggestions = [EditSuggestion(**suggestion) for suggestion in suggestions]
|
||||
|
||||
# Add assistant response to history
|
||||
if suggestions:
|
||||
for suggestion in suggestions:
|
||||
self.chat_history.append(
|
||||
Message(
|
||||
role=MessageRole.ASSISTANT,
|
||||
content=suggestion.justification,
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
)
|
||||
|
||||
return suggestions
|
||||
|
||||
def suggest_edits(
|
||||
self,
|
||||
user_query: str,
|
||||
auto_history: bool = True,
|
||||
chat_history: Optional[List[dict]] = None,
|
||||
) -> List[EditSuggestion]:
|
||||
"""Synchronous wrapper for getting edit suggestions."""
|
||||
return self._run_sync(
|
||||
self.asuggest_edits(user_query, auto_history, chat_history)
|
||||
)
|
||||
|
||||
async def await_completion(
|
||||
self, timeout: int = DEFAULT_TIMEOUT, poll_interval: int = DEFAULT_POLL_INTERVAL
|
||||
) -> Report:
|
||||
"""Wait for this report to complete processing."""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
report_response = await self.aget()
|
||||
status = report_response.status
|
||||
|
||||
if status == "completed":
|
||||
return report_response.report
|
||||
elif status == "error":
|
||||
events = await self.aget_events()
|
||||
raise ValueError(f"Report entered error state: {events[-1].msg}")
|
||||
elif time.time() - start_time > timeout:
|
||||
raise TimeoutError(f"Report did not complete within {timeout} seconds")
|
||||
|
||||
await asyncio.sleep(poll_interval)
|
||||
|
||||
def wait_for_completion(
|
||||
self, timeout: int = DEFAULT_TIMEOUT, poll_interval: int = DEFAULT_POLL_INTERVAL
|
||||
) -> Report:
|
||||
"""Synchronous wrapper for awaiting report completion."""
|
||||
return self._run_sync(self.await_completion(timeout, poll_interval))
|
||||
|
||||
async def await_for_plan(
|
||||
self, timeout: int = DEFAULT_TIMEOUT, poll_interval: int = DEFAULT_POLL_INTERVAL
|
||||
) -> ReportPlan:
|
||||
"""Wait for this report's plan to be ready for review."""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
report_metadata = await self.aget_metadata()
|
||||
state = report_metadata.state
|
||||
|
||||
if state == "waiting_approval":
|
||||
report_response = await self.aget()
|
||||
return report_response.plan
|
||||
elif state == "error":
|
||||
events = await self.aget_events()
|
||||
raise ValueError(f"Report entered error state: {events[-1].msg}")
|
||||
elif time.time() - start_time > timeout:
|
||||
raise TimeoutError(f"Plan was not ready within {timeout} seconds")
|
||||
|
||||
await asyncio.sleep(poll_interval)
|
||||
|
||||
def wait_for_plan(
|
||||
self, timeout: int = DEFAULT_TIMEOUT, poll_interval: int = DEFAULT_POLL_INTERVAL
|
||||
) -> ReportPlan:
|
||||
"""Synchronous wrapper for awaiting plan readiness."""
|
||||
return self._run_sync(self.await_for_plan(timeout, poll_interval))
|
||||
|
||||
async def aget_metadata(self) -> ReportMetadata:
|
||||
"""Get this report's metadata asynchronously."""
|
||||
return await self._client.aget_report_metadata(self.report_id)
|
||||
|
||||
def get_metadata(self) -> ReportMetadata:
|
||||
"""Synchronous wrapper for getting this report's metadata."""
|
||||
return self._run_sync(self.aget_metadata())
|
||||
|
||||
async def adelete(self) -> None:
|
||||
"""Delete this report asynchronously."""
|
||||
return await self._client.adelete_report(self.report_id)
|
||||
|
||||
def delete(self) -> None:
|
||||
"""Synchronous wrapper for deleting this report."""
|
||||
return self._run_sync(self.adelete())
|
||||
|
||||
async def aaccept_edit(self, suggestion: EditSuggestion) -> None:
|
||||
"""Accept a suggested edit.
|
||||
|
||||
Args:
|
||||
suggestion: The EditSuggestion to accept, typically from suggest_edits()
|
||||
"""
|
||||
if len(suggestion.blocks) == 0:
|
||||
return
|
||||
|
||||
# Determine if we're editing a plan or report based on first block type
|
||||
is_plan_edit = isinstance(suggestion.blocks[0], ReportPlanBlock)
|
||||
|
||||
# Get current content
|
||||
report_response = await self.aget()
|
||||
current_blocks = (
|
||||
report_response.plan.blocks
|
||||
if is_plan_edit
|
||||
else report_response.report.blocks
|
||||
)
|
||||
|
||||
# Track the edit
|
||||
new_blocks = []
|
||||
for edit_block in suggestion.blocks:
|
||||
# Find matching block in current content
|
||||
old_block = next(
|
||||
(
|
||||
b
|
||||
for b in current_blocks
|
||||
if self._get_block_idx(b) == self._get_block_idx(edit_block)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
old_content = (
|
||||
self._get_block_content(old_block) if old_block else "[No old content]"
|
||||
)
|
||||
new_content = self._get_block_content(edit_block)
|
||||
|
||||
if is_plan_edit:
|
||||
new_queries_str = "\n".join(
|
||||
[
|
||||
f"Field: {q.field}, Prompt: {q.prompt}, Context: {q.context}"
|
||||
for q in edit_block.queries
|
||||
]
|
||||
)
|
||||
new_dependency_str = (
|
||||
f"Depends on: {edit_block.dependency}"
|
||||
if edit_block.dependency
|
||||
else ""
|
||||
)
|
||||
new_content += f"\n\n{new_queries_str}\n{new_dependency_str}"
|
||||
|
||||
if old_block:
|
||||
old_queries_str = "\n".join(
|
||||
[
|
||||
f"Field: {q.field}, Prompt: {q.prompt}, Context: {q.context}"
|
||||
for q in old_block.queries
|
||||
]
|
||||
)
|
||||
old_dependency_str = (
|
||||
f"Depends on: {old_block.dependency}"
|
||||
if old_block.dependency
|
||||
else ""
|
||||
)
|
||||
old_content += f"\n\n{old_queries_str}\n{old_dependency_str}"
|
||||
|
||||
self.edit_history.append(
|
||||
EditAction(
|
||||
block_idx=self._get_block_idx(edit_block),
|
||||
old_content=old_content,
|
||||
new_content=new_content,
|
||||
action="approved",
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
)
|
||||
|
||||
# Create updated block
|
||||
if is_plan_edit:
|
||||
new_blocks.append(
|
||||
ReportPlanBlock(
|
||||
block=ReportBlock(
|
||||
idx=edit_block.block.idx,
|
||||
template=self._get_block_content(edit_block),
|
||||
sources=edit_block.block.sources,
|
||||
),
|
||||
queries=edit_block.queries,
|
||||
dependency=edit_block.dependency,
|
||||
)
|
||||
)
|
||||
else:
|
||||
new_blocks.append(
|
||||
ReportBlock(
|
||||
idx=edit_block.idx,
|
||||
template=self._get_block_content(edit_block),
|
||||
sources=edit_block.sources,
|
||||
)
|
||||
)
|
||||
|
||||
if new_blocks:
|
||||
if is_plan_edit:
|
||||
# Update plan in place
|
||||
plan = report_response.plan
|
||||
|
||||
# Replace edited blocks and add new ones
|
||||
for new_block in new_blocks:
|
||||
block_idx = self._get_block_idx(new_block)
|
||||
existing_block_idx = next(
|
||||
(
|
||||
i
|
||||
for i, b in enumerate(plan.blocks)
|
||||
if b.block.idx == block_idx
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if existing_block_idx is not None:
|
||||
# Replace existing block
|
||||
plan.blocks[existing_block_idx] = new_block
|
||||
else:
|
||||
# Add new block to end
|
||||
plan.blocks.append(new_block)
|
||||
|
||||
await self.aupdate_plan("edit", plan)
|
||||
else:
|
||||
# Update report in place
|
||||
report = report_response.report
|
||||
|
||||
# Replace edited blocks and add new ones
|
||||
for new_block in new_blocks:
|
||||
block_idx = self._get_block_idx(new_block)
|
||||
existing_block_idx = next(
|
||||
(i for i, b in enumerate(report.blocks) if b.idx == block_idx),
|
||||
None,
|
||||
)
|
||||
|
||||
if existing_block_idx is not None:
|
||||
# Replace existing block
|
||||
report.blocks[existing_block_idx] = new_block
|
||||
else:
|
||||
# Add new block to end
|
||||
report.blocks.append(new_block)
|
||||
|
||||
await self.aupdate_report(report)
|
||||
|
||||
def accept_edit(self, suggestion: EditSuggestion) -> None:
|
||||
"""Synchronous wrapper for accepting an edit."""
|
||||
return self._run_sync(self.aaccept_edit(suggestion))
|
||||
|
||||
async def areject_edit(self, suggestion: EditSuggestion) -> None:
|
||||
"""Reject a suggested edit.
|
||||
|
||||
Args:
|
||||
suggestion: The EditSuggestion to reject, typically from suggest_edits()
|
||||
"""
|
||||
# Track the rejections
|
||||
for edit_block in suggestion.blocks:
|
||||
self.edit_history.append(
|
||||
EditAction(
|
||||
block_idx=self._get_block_idx(edit_block),
|
||||
old_content=self._get_block_content(edit_block),
|
||||
new_content=None,
|
||||
action="rejected",
|
||||
timestamp=datetime.now(),
|
||||
)
|
||||
)
|
||||
|
||||
def reject_edit(self, suggestion: EditSuggestion) -> None:
|
||||
"""Synchronous wrapper for rejecting an edit."""
|
||||
return self._run_sync(self.areject_edit(suggestion))
|
||||
|
||||
def _get_edit_summary_after_message(
|
||||
self, message_timestamp: datetime
|
||||
) -> Optional[str]:
|
||||
"""Get a summary of edits that occurred after a specific message."""
|
||||
relevant_edits = [
|
||||
edit for edit in self.edit_history if edit.timestamp > message_timestamp
|
||||
]
|
||||
|
||||
if not relevant_edits:
|
||||
return None
|
||||
|
||||
approved = [edit for edit in relevant_edits if edit.action == "approved"]
|
||||
rejected = [edit for edit in relevant_edits if edit.action == "rejected"]
|
||||
|
||||
summary = []
|
||||
|
||||
if approved:
|
||||
summary.append("Approved edits:")
|
||||
for edit in approved:
|
||||
summary.append(
|
||||
f'Block {edit.block_idx}: "{edit.old_content}" -> "{edit.new_content}"'
|
||||
)
|
||||
|
||||
if rejected:
|
||||
if approved: # Add spacing if we had approved edits
|
||||
summary.append("")
|
||||
summary.append("Rejected edits:")
|
||||
for edit in rejected:
|
||||
summary.append(f'Block {edit.block_idx}: "{edit.old_content}"')
|
||||
|
||||
return "\n".join(summary)
|
||||
|
||||
async def aget_events(
|
||||
self, last_sequence: Optional[int] = None
|
||||
) -> List[ReportEventItemEventData_Progress]:
|
||||
"""Get all events for this report asynchronously.
|
||||
|
||||
Args:
|
||||
last_sequence: If provided, only get events after this sequence number
|
||||
|
||||
Returns:
|
||||
List of ReportEvent objects
|
||||
"""
|
||||
extra_params = []
|
||||
if last_sequence is not None:
|
||||
extra_params.append(f"last_sequence={last_sequence}")
|
||||
|
||||
url = await self._build_url(
|
||||
f"/api/v1/reports/{self.report_id}/events", extra_params
|
||||
)
|
||||
|
||||
response = await self.aclient.get(url, headers=self._headers)
|
||||
response.raise_for_status()
|
||||
progress_events = []
|
||||
for event in response.json():
|
||||
if event["event_type"] == "progress":
|
||||
progress_events.append(
|
||||
ReportEventItemEventData_Progress(**event["event_data"])
|
||||
)
|
||||
|
||||
return progress_events
|
||||
|
||||
def get_events(
|
||||
self, last_sequence: Optional[int] = None
|
||||
) -> List[ReportEventItemEventData_Progress]:
|
||||
"""Synchronous wrapper for getting report events."""
|
||||
return self._run_sync(self.aget_events(last_sequence))
|
||||
@@ -1,129 +0,0 @@
|
||||
import os
|
||||
import pytest
|
||||
import uuid
|
||||
from typing import AsyncGenerator
|
||||
from pytest_asyncio import fixture as async_fixture
|
||||
from llama_cloud_services.report import LlamaReport, ReportClient
|
||||
|
||||
# Skip tests if no API key is set
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not os.getenv("LLAMA_CLOUD_API_KEY") or os.getenv("CI") == "true",
|
||||
reason="No API key provided",
|
||||
)
|
||||
|
||||
|
||||
@async_fixture(scope="function")
|
||||
async def client() -> AsyncGenerator[LlamaReport, None]:
|
||||
"""Create a LlamaReport client."""
|
||||
client = LlamaReport()
|
||||
reports_before = await client.alist_reports()
|
||||
reports_before_ids = [r.report_id for r in reports_before]
|
||||
try:
|
||||
yield client
|
||||
finally:
|
||||
# clean up reports
|
||||
try:
|
||||
reports_after = await client.alist_reports()
|
||||
reports_after_ids = [r.report_id for r in reports_after]
|
||||
for report_id in reports_before_ids:
|
||||
if report_id not in reports_after_ids:
|
||||
await client.adelete_report(report_id)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
await client.aclient.aclose()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def unique_name() -> str:
|
||||
"""Generate a unique report name."""
|
||||
return f"test-report-{uuid.uuid4()}"
|
||||
|
||||
|
||||
@async_fixture(scope="function")
|
||||
async def report(
|
||||
client: LlamaReport, unique_name: str
|
||||
) -> AsyncGenerator[ReportClient, None]:
|
||||
"""Create a report."""
|
||||
report = await client.acreate_report(
|
||||
name=unique_name,
|
||||
template_text=(
|
||||
"# [Some title]\n\n"
|
||||
" ## TLDR\n"
|
||||
"A quick summary of the paper.\n\n"
|
||||
"## Details\n"
|
||||
"More details about the paper, possible more than one section here.\n"
|
||||
),
|
||||
input_files=["tests/test_files/paper.md"],
|
||||
)
|
||||
try:
|
||||
yield report
|
||||
finally:
|
||||
await report.adelete()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.xfail(
|
||||
condition=lambda: os.getenv("CI"),
|
||||
reason="Backend db issues; needs to be fixed.",
|
||||
)
|
||||
async def test_create_and_delete_report(
|
||||
client: LlamaReport, report: ReportClient
|
||||
) -> None:
|
||||
"""Test basic report creation and deletion."""
|
||||
# Verify the report exists
|
||||
metadata = await report.aget_metadata()
|
||||
assert metadata.name == report.name
|
||||
|
||||
# Test listing reports
|
||||
reports = await client.alist_reports()
|
||||
assert any(r.report_id == report.report_id for r in reports)
|
||||
|
||||
# Test getting report by ID
|
||||
fetched_report = await client.aget_report(report.report_id)
|
||||
assert fetched_report.report_id == report.report_id
|
||||
assert fetched_report.name == report.name
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.xfail(
|
||||
condition=lambda: os.getenv("CI"),
|
||||
reason="Report plan sometimes times out",
|
||||
raises=TimeoutError,
|
||||
)
|
||||
async def test_report_plan_workflow(report: ReportClient) -> None:
|
||||
"""Test the report planning workflow."""
|
||||
# Wait for the plan
|
||||
plan = await report.await_for_plan()
|
||||
assert plan is not None
|
||||
|
||||
# Approve the plan
|
||||
response = await report.aupdate_plan(action="approve")
|
||||
assert response is not None
|
||||
|
||||
# Wait for completion
|
||||
completed_report = await report.await_completion()
|
||||
assert len(completed_report.blocks) > 0
|
||||
|
||||
# Get edit suggestions
|
||||
suggestions = await report.asuggest_edits(
|
||||
"TLDR section header more formal.", auto_history=True
|
||||
)
|
||||
assert len(suggestions) > 0
|
||||
|
||||
# Test accepting an edit
|
||||
await report.aaccept_edit(suggestions[0])
|
||||
|
||||
# Get more suggestions and test rejecting
|
||||
more_suggestions = await report.asuggest_edits(
|
||||
"Add a section about machine learning.", auto_history=True
|
||||
)
|
||||
assert len(more_suggestions) > 0
|
||||
await report.areject_edit(more_suggestions[0])
|
||||
|
||||
# Verify chat history is maintained
|
||||
assert len(report.chat_history) >= 4 # 2 user messages + 2 assistant responses
|
||||
|
||||
# get events
|
||||
events = await report.aget_events()
|
||||
assert len(events) > 0
|
||||
Generated
+2256
-2256
File diff suppressed because it is too large
Load Diff
@@ -1,101 +0,0 @@
|
||||
# LlamaReport (beta/invite-only)
|
||||
|
||||
LlamaReport is a prebuilt agentic report builder that can be used to build reports from a variety of data sources.
|
||||
|
||||
The python SDK for interacting with the LlamaReport API. The SDK provides two main classes:
|
||||
|
||||
- `LlamaReport`: For managing reports (create, list, delete)
|
||||
- `ReportClient`: For working with a specific report (editing, approving, etc.)
|
||||
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
pip install llama-cloud-services
|
||||
```
|
||||
|
||||
```python
|
||||
from llama_cloud_services import LlamaReport
|
||||
|
||||
# Initialize the client
|
||||
client = LlamaReport(
|
||||
api_key="your-api-key",
|
||||
# Optional: Specify project_id, organization_id, async_httpx_client
|
||||
)
|
||||
|
||||
# Create a new report
|
||||
report = client.create_report(
|
||||
"My Report",
|
||||
# must have one of template_text or template_instructions
|
||||
template_text="Your template text",
|
||||
template_instructions="Instructions for the template",
|
||||
# must have one of input_files or retriever_id
|
||||
input_files=["data1.pdf", "data2.pdf"],
|
||||
retriever_id="retriever-id",
|
||||
)
|
||||
```
|
||||
|
||||
## Working with Reports
|
||||
|
||||
The typical workflow for a report involves:
|
||||
|
||||
1. Creating the report
|
||||
2. Waiting for and approving the plan
|
||||
3. Waiting for report generation
|
||||
4. Making edits to the report
|
||||
|
||||
Here's a complete example:
|
||||
|
||||
```python
|
||||
# Create a report
|
||||
report = client.create_report(
|
||||
"Quarterly Analysis", input_files=["q1_data.pdf", "q2_data.pdf"]
|
||||
)
|
||||
|
||||
# Wait for the plan to be ready
|
||||
plan = report.wait_for_plan()
|
||||
|
||||
# Option 1: Directly approve the plan
|
||||
report.update_plan(action="approve")
|
||||
|
||||
# Option 2: Suggest and review edits to the plan
|
||||
suggestions = report.suggest_edits(
|
||||
"Can you add a section about market trends?"
|
||||
)
|
||||
for suggestion in suggestions:
|
||||
print(suggestion)
|
||||
|
||||
# Accept or reject the suggestion
|
||||
if input("Accept? (y/n): ").lower() == "y":
|
||||
report.accept_edit(suggestion)
|
||||
else:
|
||||
report.reject_edit(suggestion)
|
||||
|
||||
# Wait for the report to complete
|
||||
report = report.wait_for_completion()
|
||||
|
||||
# Make edits to the final report
|
||||
suggestions = report.suggest_edits("Make the executive summary more concise")
|
||||
|
||||
# Review and accept/reject suggestions as above
|
||||
...
|
||||
```
|
||||
|
||||
### Getting the Final Report
|
||||
|
||||
Once you are satisfied with the report, you can get the final report object and use the content as you see fit.
|
||||
|
||||
Here's an example of printing out the final report:
|
||||
|
||||
```python
|
||||
report = report.get()
|
||||
report_text = "\n\n".join([block.template for block in report.blocks])
|
||||
|
||||
print(report_text)
|
||||
```
|
||||
|
||||
## Additional Features
|
||||
|
||||
- **Async Support**: All methods have async counterparts: `create_report` -> `acreate_report`, `wait_for_plan` -> `await_for_plan`, etc.
|
||||
- **Automatic Chat History**: The SDK automatically keeps track of chat history for each suggestion, unless you specify `auto_history=False` in `suggest_edits`.
|
||||
- **Custom HTTP Client**: You can provide your own `httpx.AsyncClient` to the `LlamaReport` class.
|
||||
- **Project and Organization IDs**: You can specify `project_id` and `organization_id` to use a specific project or organization.
|
||||
Reference in New Issue
Block a user