From c8fd51e8c8a1021b94efbf8928ca8710480cbbc6 Mon Sep 17 00:00:00 2001 From: junkeon <35945268+junkeon@users.noreply.github.com> Date: Thu, 25 Apr 2024 07:17:20 +0900 Subject: [PATCH] upstage: Add Upstage partner package LA and GC (#20651) --------- Co-authored-by: Sean Co-authored-by: Erick Friis Co-authored-by: Sean Cho --- .github/workflows/_release.yml | 2 +- cookbook/README.md | 1 + ...e_layout_analysis_groundedness_check.ipynb | 85 ++++ .../document_loaders/upstage.ipynb | 120 ++++++ .../docs/integrations/providers/upstage.ipynb | 144 ++++++- .../tools/upstage_groundedness_check.ipynb | 150 +++++++ docs/vercel_build.sh | 2 + .../upstage/langchain_upstage/__init__.py | 11 +- .../langchain_upstage/layout_analysis.py | 190 +++++++++ .../layout_analysis_parsers.py | 375 ++++++++++++++++++ .../tools/groundedness_check.py | 91 +++++ libs/partners/upstage/poetry.lock | 86 +++- libs/partners/upstage/pyproject.toml | 3 + .../partners/upstage/tests/examples/solar.pdf | Bin 0 -> 111524 bytes .../test_groundness_check.py | 17 + .../unit_tests/test_groundedness_check.py | 10 + .../upstage/tests/unit_tests/test_imports.py | 3 + .../tests/unit_tests/test_layout_analysis.py | 200 ++++++++++ 18 files changed, 1471 insertions(+), 19 deletions(-) create mode 100644 cookbook/rag_upstage_layout_analysis_groundedness_check.ipynb create mode 100644 docs/docs/integrations/document_loaders/upstage.ipynb create mode 100644 docs/docs/integrations/tools/upstage_groundedness_check.ipynb create mode 100644 libs/partners/upstage/langchain_upstage/layout_analysis.py create mode 100644 libs/partners/upstage/langchain_upstage/layout_analysis_parsers.py create mode 100644 libs/partners/upstage/langchain_upstage/tools/groundedness_check.py create mode 100644 libs/partners/upstage/tests/examples/solar.pdf create mode 100644 libs/partners/upstage/tests/integration_tests/test_groundness_check.py create mode 100644 libs/partners/upstage/tests/unit_tests/test_groundedness_check.py create mode 100644 libs/partners/upstage/tests/unit_tests/test_layout_analysis.py diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index 417400d3b9..38e882a7e7 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -112,7 +112,7 @@ jobs: PKG_NAME: ${{ needs.build.outputs.pkg-name }} VERSION: ${{ needs.build.outputs.version }} # Here we use: - # - The default regular PyPI index as the *primary* index, meaning + # - The default regular PyPI index as the *primary* index, meaning # that it takes priority (https://pypi.org/simple) # - The test PyPI index as an extra index, so that any dependencies that # are not found on test PyPI can be resolved and installed anyway. diff --git a/cookbook/README.md b/cookbook/README.md index aebb3e3683..87437895e8 100644 --- a/cookbook/README.md +++ b/cookbook/README.md @@ -47,6 +47,7 @@ Notebook | Description [press_releases.ipynb](https://github.com/langchain-ai/langchain/tree/master/cookbook/press_releases.ipynb) | Retrieve and query company press release data powered by [Kay.ai](https://kay.ai). [program_aided_language_model.i...](https://github.com/langchain-ai/langchain/tree/master/cookbook/program_aided_language_model.ipynb) | Implement program-aided language models as described in the provided research paper. [qa_citations.ipynb](https://github.com/langchain-ai/langchain/tree/master/cookbook/qa_citations.ipynb) | Different ways to get a model to cite its sources. +[rag_upstage_layout_analysis_groundedness_check.ipynb](https://github.com/langchain-ai/langchain/tree/master/cookbook/rag_upstage_layout_analysis_groundedness_check.ipynb) | End-to-end RAG example using Upstage Layout Analysis and Groundedness Check. [retrieval_in_sql.ipynb](https://github.com/langchain-ai/langchain/tree/master/cookbook/retrieval_in_sql.ipynb) | Perform retrieval-augmented-generation (rag) on a PostgreSQL database using pgvector. [sales_agent_with_context.ipynb](https://github.com/langchain-ai/langchain/tree/master/cookbook/sales_agent_with_context.ipynb) | Implement a context-aware ai sales agent, salesgpt, that can have natural sales conversations, interact with other systems, and use a product knowledge base to discuss a company's offerings. [self_query_hotel_search.ipynb](https://github.com/langchain-ai/langchain/tree/master/cookbook/self_query_hotel_search.ipynb) | Build a hotel room search feature with self-querying retrieval, using a specific hotel recommendation dataset. diff --git a/cookbook/rag_upstage_layout_analysis_groundedness_check.ipynb b/cookbook/rag_upstage_layout_analysis_groundedness_check.ipynb new file mode 100644 index 0000000000..189f421330 --- /dev/null +++ b/cookbook/rag_upstage_layout_analysis_groundedness_check.ipynb @@ -0,0 +1,85 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# RAG using Upstage Layout Analysis and Groundedness Check\n", + "This example illustrates RAG using [Upstage](https://python.langchain.com/docs/integrations/providers/upstage/) Layout Analysis and Groundedness Check." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "\n", + "from langchain_community.vectorstores import DocArrayInMemorySearch\n", + "from langchain_core.documents.base import Document\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langchain_core.runnables.base import RunnableSerializable\n", + "from langchain_upstage import (\n", + " ChatUpstage,\n", + " GroundednessCheck,\n", + " UpstageEmbeddings,\n", + " UpstageLayoutAnalysisLoader,\n", + ")\n", + "\n", + "model = ChatUpstage()\n", + "\n", + "files = [\"/PATH/TO/YOUR/FILE.pdf\", \"/PATH/TO/YOUR/FILE2.pdf\"]\n", + "\n", + "loader = UpstageLayoutAnalysisLoader(file_path=files, split=\"element\")\n", + "\n", + "docs = loader.load()\n", + "\n", + "vectorstore = DocArrayInMemorySearch.from_documents(docs, embedding=UpstageEmbeddings())\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "template = \"\"\"Answer the question based only on the following context:\n", + "{context}\n", + "\n", + "Question: {question}\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_template(template)\n", + "output_parser = StrOutputParser()\n", + "\n", + "retrieved_docs = retriever.get_relevant_documents(\"How many parameters in SOLAR model?\")\n", + "\n", + "groundedness_check = GroundednessCheck()\n", + "groundedness = \"\"\n", + "while groundedness != \"grounded\":\n", + " chain: RunnableSerializable = RunnablePassthrough() | prompt | model | output_parser\n", + "\n", + " result = chain.invoke(\n", + " {\n", + " \"context\": retrieved_docs,\n", + " \"question\": \"How many parameters in SOLAR model?\",\n", + " }\n", + " )\n", + "\n", + " # convert all Documents to string\n", + " def formatDocumentsAsString(docs: List[Document]) -> str:\n", + " return \"\\n\".join([doc.page_content for doc in docs])\n", + "\n", + " groundedness = groundedness_check.run(\n", + " {\n", + " \"context\": formatDocumentsAsString(retrieved_docs),\n", + " \"query\": result,\n", + " }\n", + " )" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/docs/integrations/document_loaders/upstage.ipynb b/docs/docs/integrations/document_loaders/upstage.ipynb new file mode 100644 index 0000000000..792e3cfd8e --- /dev/null +++ b/docs/docs/integrations/document_loaders/upstage.ipynb @@ -0,0 +1,120 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "910f5772b6af13c9", + "metadata": { + "collapsed": false + }, + "source": [ + "---\n", + "sidebar_label: Upstage\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "433f5422ad8e1efa", + "metadata": { + "collapsed": false + }, + "source": [ + "# UpstageLayoutAnalysisLoader\n", + "\n", + "This notebook covers how to get started with `UpstageLayoutAnalysisLoader`.\n", + "\n", + "## Installation\n", + "\n", + "Install `langchain-upstage` package.\n", + "\n", + "```bash\n", + "pip install -U langchain-upstage\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "e6e5941c", + "metadata": {}, + "source": [ + "## Environment Setup\n", + "\n", + "Make sure to set the following environment variables:\n", + "\n", + "- `UPSTAGE_DOCUMENT_AI_API_KEY`: Your Upstage Document AI API key. Read [Upstage developers document](https://developers.upstage.ai/docs/getting-started/quick-start) to get your API key.\n", + "\n", + "> As of April 2024, you need separate access tokens for Solar and Layout Analysis. The access tokens will be consolidated soon (hopefully in May) and you'll need just one key for all features." + ] + }, + { + "cell_type": "markdown", + "id": "21e72f3d", + "metadata": {}, + "source": [ + "## Usage" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a05efd34", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"UPSTAGE_DOCUMENT_AI_API_KEY\"] = \"YOUR_API_KEY\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2b914a7b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "page_content='SOLAR 10.7B: Scaling Large Language Models with Simple yet Effective\\nDepth Up-Scaling Dahyun Kim* , Chanjun Park*1, Sanghoon Kim*+, Wonsung Lee*†, Wonho Song*\\nYunsu Kim* , Hyeonwoo Kim* , Yungi Kim, Hyeonju Lee, Jihoo Kim\\nChangbae Ahn, Seonghoon Yang, Sukyung Lee, Hyunbyung Park, Gyoungjin Gim\\nMikyoung Cha, Hwalsuk Leet , Sunghun Kim+ Upstage AI, South Korea {kdahyun, chan jun · park, limerobot, wonsung · lee, hwalsuk lee, hunkim} @ upstage · ai Abstract We introduce SOLAR 10.7B, a large language\\nmodel (LLM) with 10.7 billion parameters,\\ndemonstrating superior performance in various\\nnatural language processing (NLP) tasks. In-\\nspired by recent efforts to efficiently up-scale\\nLLMs, we present a method for scaling LLMs\\ncalled depth up-scaling (DUS), which encom-\\npasses depthwise scaling and continued pre-\\ntraining. In contrast to other LLM up-scaling\\nmethods that use mixture-of-experts, DUS does\\nnot require complex changes to train and infer-\\nence efficiently. We show experimentally that\\nDUS is simple yet effective in scaling up high-\\nperformance LLMs from small ones. Building\\non the DUS model, we additionally present SO-\\nLAR 10.7B-Instruct, a variant fine-tuned for\\ninstruction-following capabilities, surpassing\\nMixtral-8x7B-Instruct. SOLAR 10.7B is pub-\\nlicly available under the Apache 2.0 license,\\npromoting broad access and application in the\\nLLM field 1 1 Introduction The field of natural language processing (NLP)\\nhas been significantly transformed by the introduc-\\ntion of large language models (LLMs), which have\\nenhanced our understanding and interaction with\\nhuman language (Zhao et al., 2023). These ad-\\nvancements bring challenges such as the increased\\nneed to train ever larger models (Rae et al., 2021;\\nWang et al., 2023; Pan et al., 2023; Lian, 2023;\\nYao et al., 2023; Gesmundo and Maile, 2023) OW-\\ning to the performance scaling law (Kaplan et al.,\\n2020; Hernandez et al., 2021; Anil et al., 2023;\\nKaddour et al., 2023). To efficiently tackle the\\nabove, recent works in scaling language models\\nsuch as a mixture of experts (MoE) (Shazeer et al.,\\n2017; Komatsuzaki et al., 2022) have been pro-\\nposed. While those approaches are able to effi- ciently and effectively scale-up LLMs, they often\\nrequire non-trivial changes to the training and infer-\\nence framework (Gale et al., 2023), which hinders\\nwidespread applicability. Effectively and efficiently\\nscaling up LLMs whilst also retaining the simplic-\\nity for ease of use is an important problem (Alberts\\net al., 2023; Fraiwan and Khasawneh, 2023; Sallam\\net al., 2023; Bahrini et al., 2023). Inspired by Komatsuzaki et al. (2022), we\\npresent depth up-scaling (DUS), an effective and\\nefficient method to up-scale LLMs whilst also re-\\nmaining straightforward to use. DUS consists of\\nscaling the number of layers in the base model and\\ncontinually pretraining the scaled model. Unlike\\n(Komatsuzaki et al., 2022), DUS does not scale\\nthe model using MoE and rather use a depthwise\\nscaling method analogous to Tan and Le (2019)\\nwhich is adapted for the LLM architecture. Thus,\\nthere are no additional modules or dynamism as\\nwith MoE, making DUS immediately compatible\\nwith easy-to-use LLM frameworks such as Hug-\\ngingFace (Wolf et al., 2019) with no changes to\\nthe training or inference framework for maximal\\nefficiency. Furthermore, DUS is applicable to all\\ntransformer architectures, opening up new gate-\\nways to effectively and efficiently scale-up LLMs\\nin a simple manner. Using DUS, we release SO-\\nLAR 10.7B, an LLM with 10.7 billion parameters,\\nthat outperforms existing models like Llama 2 (Tou-\\nvron et al., 2023) and Mistral 7B (Jiang et al., 2023)\\nin various benchmarks. We have also developed SOLAR 10.7B-Instruct,\\na variant fine-tuned for tasks requiring strict adher-\\nence to complex instructions. It significantly out-\\nperforms the Mixtral-8x7B-Instruct model across\\nvarious evaluation metrics, evidencing an advanced\\nproficiency that exceeds the capabilities of even\\nlarger models in terms of benchmark performance. * Equal Contribution 1 Corresponding Author\\nhttps : / /huggingface.co/upstage/\\nSOLAR-1 0 · 7B-v1 . 0 By releasing SOLAR 10.7B under the Apache\\n2.0 license, we aim to promote collaboration and in-\\nnovation in NLP. This open-source approach allows 2024\\nApr\\n4\\n[cs.CL]\\narxiv:2...117.7.13' metadata={'page': 1, 'type': 'text', 'split': 'page'}\n", + "page_content=\"Step 1-1 Step 1-2\\nOutput Output Output\\nOutput Output Output\\n24 Layers 24Layers\\nMerge\\n8Layers\\n---- 48 Layers\\nCopy\\n8 Layers Continued\\n32Layers 32Layers\\nPretraining\\n24Layers\\n24 Layers Input\\nInput Input Input Input Input\\nStep 1. Depthwise Scaling Step2. Continued Pretraining Figure 1: Depth up-scaling for the case with n = 32, s = 48, and m = 8. Depth up-scaling is achieved through a\\ndual-stage process of depthwise scaling followed by continued pretraining. for wider access and application of these models\\nby researchers and developers globally. 2 Depth Up-Scaling To efficiently scale-up LLMs, we aim to utilize pre-\\ntrained weights of base models to scale up to larger\\nLLMs (Komatsuzaki et al., 2022). While exist-\\ning methods such as Komatsuzaki et al. (2022) use\\nMoE (Shazeer et al., 2017) to scale-up the model ar-\\nchitecture, we opt for a different depthwise scaling\\nstrategy inspired by Tan and Le (2019). We then\\ncontinually pretrain the scaled model as just scaling\\nthe model without further pretraining degrades the\\nperformance. Base model. Any n-layer transformer architec-\\nture can be used but we select the 32-layer Llama\\n2 architecture as our base model. We initialize the\\nLlama 2 architecture with pretrained weights from\\nMistral 7B, as it is one of the top performers com-\\npatible with the Llama 2 architecture. By adopting\\nthe Llama 2 architecture for our base model, we\\naim to leverage the vast pool of community re-\\nsources while introducing novel modifications to\\nfurther enhance its capabilities. Depthwise scaling. From the base model with n\\nlayers, we set the target layer count s for the scaled\\nmodel, which is largely dictated by the available\\nhardware. With the above, the depthwise scaling process\\nis as follows. The base model with n layers is\\nduplicated for subsequent modification. Then, we\\nremove the final m layers from the original model\\nand the initial m layers from its duplicate, thus\\nforming two distinct models with n - m layers.\\nThese two models are concatenated to form a scaled\\nmodel with s = 2· (n-m) layers. Note that n = 32\\nfrom our base model and we set s = 48 considering our hardware constraints and the efficiency of the\\nscaled model, i.e., fitting between 7 and 13 billion\\nparameters. Naturally, this leads to the removal of\\nm = 8 layers. The depthwise scaling process with\\nn = 32, s = 48, and m = 8 is depicted in 'Step 1:\\nDepthwise Scaling' of Fig. 1. We note that a method in the community that also\\n2 'Step 1:\\nscale the model in the same manner as\\nDepthwise Scaling' of Fig. 1 has been concurrently\\ndeveloped. Continued pretraining. The performance of the\\ndepthwise scaled model initially drops below that\\nof the base LLM. Thus, we additionally apply\\nthe continued pretraining step as shown in 'Step\\n2: Continued Pretraining' of Fig. 1. Experimen-\\ntally, we observe rapid performance recovery of\\nthe scaled model during continued pretraining, a\\nphenomenon also observed in Komatsuzaki et al.\\n(2022). We consider that the particular way of\\ndepthwise scaling has isolated the heterogeneity\\nin the scaled model which allowed for this fast\\nperformance recovery. Delving deeper into the heterogeneity of the\\nscaled model, a simpler alternative to depthwise\\nscaling could be to just repeat its layers once more,\\ni.e., from n to 2n layers. Then, the 'layer distance',\\nor the difference in the layer indices in the base\\nmodel, is only bigger than 1 where layers n and\\nn + 1 are connected, i.e., at the seam. However, this results in maximum layer distance\\nat the seam, which may be too significant of a\\ndiscrepancy for continued pretraining to quickly\\nresolve. Instead, depthwise scaling sacrifices the\\n2m middle layers, thereby reducing the discrep-\\nancy at the seam and making it easier for continued 2https : / /huggingface · co/Undi 95/\\nMistral-11B-v0 · 1\" metadata={'page': 2, 'type': 'text', 'split': 'page'}\n", + "page_content=\"Properties Instruction Training Datasets Alignment\\n Alpaca-GPT4 OpenOrca Synth. Math-Instruct Orca DPO Pairs Ultrafeedback Cleaned Synth. Math-Alignment\\n Total # Samples 52K 2.91M 126K 12.9K 60.8K 126K\\n Maximum # Samples Used 52K 100K 52K 12.9K 60.8K 20.1K\\n Open Source O O X O O Table 1: Training datasets used for the instruction and alignment tuning stages, respectively. For the instruction\\ntuning process, we utilized the Alpaca-GPT4 (Peng et al., 2023), OpenOrca (Mukherjee et al., 2023), and Synth.\\nMath-Instruct datasets, while for the alignment tuning, we employed the Orca DPO Pairs (Intel, 2023), Ultrafeedback\\nCleaned (Cui et al., 2023; Ivison et al., 2023), and Synth. Math-Alignment datasets. The 'Total # Samples indicates\\nthe total number of samples in the entire dataset. The 'Maximum # Samples Used' indicates the actual maximum\\nnumber of samples that were used in training, which could be lower than the total number of samples in a given\\ndataset. 'Open Source' indicates whether the dataset is open-sourced. pretraining to quickly recover performance. We\\nattribute the success of DUS to reducing such dis-\\ncrepancies in both the depthwise scaling and the\\ncontinued pretraining steps. We also hypothesize\\nthat other methods of depthwise scaling could also\\nwork for DUS, as long as the discrepancy in the\\nscaled model is sufficiently contained before the\\ncontinued pretraining step. Comparison to other up-scaling methods. Un-\\nlike Komatsuzaki et al. (2022), depthwise scaled\\nmodels do not require additional modules like gat-\\ning networks or dynamic expert selection. Conse-\\nquently, scaled models in DUS do not necessitate\\na distinct training framework for optimal training\\nefficiency, nor do they require specialized CUDA\\nkernels for fast inference. A DUS model can seam-\\nlessly integrate into existing training and inference\\nframeworks while maintaining high efficiency. 3 Training Details After DUS, including continued pretraining, we\\nperform fine-tuning of SOLAR 10.7B in two stages:\\n1) instruction tuning and 2) alignment tuning. Instruction tuning. In the instruction tuning\\nstage, the model is trained to follow instructions in\\na QA format (Zhang et al., 2023). We mostly use\\nopen-source datasets but also synthesize a math QA\\ndataset to enhance the model's mathematical capa-\\nbilities. A rundown of how we crafted the dataset is\\nas follows. First, seed math data are collected from\\nthe Math (Hendrycks et al., 2021) dataset only, to\\navoid contamination with commonly used bench-\\nmark datasets such as GSM8K (Cobbe et al., 2021).\\nThen, using a process similar to MetaMath (Yu\\net al., 2023), we rephrase the questions and an-\\nswers of the seed math data. We use the resulting\\nrephrased question-answer pairs as a QA dataset and call it 'Synth. Math-Instruct*. Alignment tuning. In the alignment tuning stage,\\nthe instruction-tuned model is further fine-tuned\\nto be more aligned with human or strong AI\\n(e.g., GPT4 (OpenAI, 2023)) preferences using\\nsDPO (Kim et al., 2024a), an improved version\\nof direct preference optimization (DPO) (Rafailov\\net al., 2023). Similar to the instruction tuning stage,\\nwe use mostly open-source datasets but also syn-\\nthesize a math-focused alignment dataset utilizing\\nthe 'Synth. Math-Instruct' dataset mentioned in the\\ninstruction tuning stage. The alignment data synthesis process is as\\nfollows. We take advantage of the fact that\\nthe rephrased question-answer pairs in Synth.\\nMath-Instruct data are beneficial in enhancing the\\nmodel's mathematical capabilities (see Sec. 4.3.1).\\nThus, we speculate that the rephrased answer to the\\nrephrased question is a better answer than the orig-\\ninal answer, possibly due to the interim rephrasing\\nstep. Consequently, we set the rephrased question\\nas the prompt and use the rephrased answer as the\\nchosen response and the original answer as the re-\\njected response and create the {prompt, chosen,\\nrejected} DPO tuple. We aggregate the tuples from\\nthe rephrased question-answer pairs and call the\\nresulting dataset 'Synth. Math-Alignment*. 4 Results 4.1 Experimental Details Training datasets. We present details regarding\\nour training datasets for the instruction and align-\\nment tuning stages in Tab. 1. We do not always\\nuse the entire dataset and instead subsample a set\\namount. Note that most of our training data is\\nopen-source, and the undisclosed datasets can be\\nsubstituted for open-source alternatives such as the\" metadata={'page': 3, 'type': 'text', 'split': 'page'}\n" + ] + } + ], + "source": [ + "from langchain_upstage import UpstageLayoutAnalysisLoader\n", + "\n", + "file_path = \"/PATH/TO/YOUR/FILE.pdf\"\n", + "layzer = UpstageLayoutAnalysisLoader(file_path, split=\"page\")\n", + "\n", + "# For improved memory efficiency, consider using the lazy_load method to load documents page by page.\n", + "docs = layzer.load() # or layzer.lazy_load()\n", + "\n", + "for doc in docs[:3]:\n", + " print(doc)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/integrations/providers/upstage.ipynb b/docs/docs/integrations/providers/upstage.ipynb index e1f79c8666..e3571e63ae 100644 --- a/docs/docs/integrations/providers/upstage.ipynb +++ b/docs/docs/integrations/providers/upstage.ipynb @@ -15,7 +15,9 @@ "source": [ "## Solar LLM\n", "\n", - "**Solar Mini Chat** is a fast yet powerful advanced large language model focusing on English and Korean. It has been specifically fine-tuned for multi-turn chat purposes, showing enhanced performance across a wide range of natural language processing tasks, like multi-turn conversation or tasks that require an understanding of long contexts, such as RAG (Retrieval-Augmented Generation), compared to other models of a similar size. This fine-tuning equips it with the ability to handle longer conversations more effectively, making it particularly adept for interactive applications." + "**Solar Mini Chat** is a fast yet powerful advanced large language model focusing on English and Korean. It has been specifically fine-tuned for multi-turn chat purposes, showing enhanced performance across a wide range of natural language processing tasks, like multi-turn conversation or tasks that require an understanding of long contexts, such as RAG (Retrieval-Augmented Generation), compared to other models of a similar size. This fine-tuning equips it with the ability to handle longer conversations more effectively, making it particularly adept for interactive applications.\n", + "\n", + "Other than Solar, Upstage also offers features for real-world RAG (retrieval-augmented generation), such as **Groundedness Check** and **Layout Analysis**. " ] }, { @@ -35,7 +37,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Get an [access token](https://console.upstage.ai) and set it as an environment variable (`UPSTAGE_API_KEY`)" + "Get [API Keys](https://console.upstage.ai) and set environment variables `UPSTAGE_API_KEY` and `UPSTAGE_DOCUMENT_AI_API_KEY`.\n", + "\n", + "> As of April 2024, you need separate API Keys for Solar and Document AI(Layout Analysis). The API Keys will be consolidated soon (hopefully in May) and you'll need just one key for all features." ] }, { @@ -48,6 +52,8 @@ "| --- | --- | --- | --- |\n", "| Chat | Build assistants using Solar Mini Chat | `from langchain_upstage import ChatUpstage` | [Go](../../chat/upstage) |\n", "| Text Embedding | Embed strings to vectors | `from langchain_upstage import UpstageEmbeddings` | [Go](../../text_embedding/upstage) |\n", + "| Groundedness Check | Verify groundedness of assistant's response | `from langchain_upstage import GroundednessCheck` | [Go](../../tools/upstage_groundedness_check) |\n", + "| Layout Analysis | Serialize documents with tables and figures | `from langchain_upstage import UpstageLayoutAnalysis` | [Go](../../document_loaders/upstage) |\n", "\n", "See [documentations](https://developers.upstage.ai/) for more details about the features." ] @@ -69,7 +75,8 @@ "source": [ "import os\n", "\n", - "os.environ[\"UPSTAGE_API_KEY\"] = \"YOUR_API_KEY\"" + "os.environ[\"UPSTAGE_API_KEY\"] = \"YOUR_API_KEY\"\n", + "os.environ[\"UPSTAGE_DOCUMENT_AI_API_KEY\"] = \"YOUR_DOCUMENT_AI_API_KEY\"" ] }, { @@ -120,6 +127,137 @@ "query_result = embeddings.embed_query(\"What does Sam do?\")\n", "print(query_result)" ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "### Groundedness Check" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from langchain_upstage import GroundednessCheck\n", + "\n", + "groundedness_check = GroundednessCheck()\n", + "\n", + "request_input = {\n", + " \"context\": \"Mauna Kea is an inactive volcano on the island of Hawaii. Its peak is 4,207.3 m above sea level, making it the highest point in Hawaii and second-highest peak of an island on Earth.\",\n", + " \"query\": \"Mauna Kea is 5,207.3 meters tall.\",\n", + "}\n", + "response = groundedness_check.run(request_input)\n", + "print(response)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "### Layout Analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_upstage import UpstageLayoutAnalysis\n", + "\n", + "file_path = \"/PATH/TO/YOUR/FILE.pdf\"\n", + "layzer = UpstageLayoutAnalysis(file_path, split=\"page\")\n", + "\n", + "# For improved memory efficiency, consider using the lazy_load method to load documents page by page.\n", + "docs = layzer.load() # or layzer.lazy_load()\n", + "\n", + "for doc in docs[:3]:\n", + " print(doc)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieval-Augmented Generation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "\n", + "from langchain_community.vectorstores import DocArrayInMemorySearch\n", + "from langchain_core.documents.base import Document\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnablePassthrough\n", + "from langchain_core.runnables.base import RunnableSerializable\n", + "from langchain_upstage import (\n", + " ChatUpstage,\n", + " GroundednessCheck,\n", + " UpstageEmbeddings,\n", + " UpstageLayoutAnalysis,\n", + ")\n", + "\n", + "model = ChatUpstage()\n", + "\n", + "file_path = (\n", + " \"/PATH/TO/YOUR/FILE.pdf\" # e.g. \"libs/partners/upstage/tests/examples/solar.pdf\"\n", + ")\n", + "loader = UpstageLayoutAnalysis(file_path=file_path, split=\"element\")\n", + "docs = loader.load()\n", + "\n", + "vectorstore = DocArrayInMemorySearch.from_documents(docs, embedding=UpstageEmbeddings())\n", + "retriever = vectorstore.as_retriever()\n", + "\n", + "template = \"\"\"Answer the question based only on the following context:\n", + "{context}\n", + "\n", + "Question: {question}\n", + "\"\"\"\n", + "prompt = ChatPromptTemplate.from_template(template)\n", + "output_parser = StrOutputParser()\n", + "\n", + "question = \"ASK ANY QUESTION HERE.\" # e.g. \"How many parameters in SOLAR model?\"\n", + "\n", + "retrieved_docs = retriever.get_relevant_documents(question)\n", + "\n", + "groundedness_check = GroundednessCheck()\n", + "groundedness = \"\"\n", + "while groundedness != \"grounded\":\n", + " chain: RunnableSerializable = RunnablePassthrough() | prompt | model | output_parser\n", + "\n", + " result = chain.invoke(\n", + " {\n", + " \"context\": retrieved_docs,\n", + " \"question\": question,\n", + " }\n", + " )\n", + "\n", + " # convert all Documents to string\n", + " def formatDocumentsAsString(docs: List[Document]) -> str:\n", + " return \"\\n\".join([doc.page_content for doc in docs])\n", + "\n", + " groundedness = groundedness_check.run(\n", + " {\n", + " \"context\": formatDocumentsAsString(retrieved_docs),\n", + " \"assistant_message\": result,\n", + " }\n", + " ).content" + ] } ], "metadata": { diff --git a/docs/docs/integrations/tools/upstage_groundedness_check.ipynb b/docs/docs/integrations/tools/upstage_groundedness_check.ipynb new file mode 100644 index 0000000000..5a35e0e257 --- /dev/null +++ b/docs/docs/integrations/tools/upstage_groundedness_check.ipynb @@ -0,0 +1,150 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "99a6719c8eff9f2c", + "metadata": { + "collapsed": false + }, + "source": [ + "---\n", + "sidebar_label: Upstage\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "e499673dec883096", + "metadata": { + "collapsed": false + }, + "source": [ + "# Upstage Groundedness Check\n", + "\n", + "This notebook covers how to get started with Upstage groundedness check models.\n", + "\n", + "## Installation \n", + "\n", + "Install `langchain-upstage` package.\n", + "\n", + "```bash\n", + "pip install -U langchain-upstage\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "fdcbdec7dbe5164f", + "metadata": { + "collapsed": false + }, + "source": [ + "## Environment Setup\n", + "\n", + "Make sure to set the following environment variables:\n", + "\n", + "- `UPSTAGE_API_KEY`: Your Upstage API key from [Upstage developers document](https://developers.upstage.ai/docs/getting-started/quick-start)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a83d4da0", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"UPSTAGE_API_KEY\"] = \"YOUR_API_KEY\"" + ] + }, + { + "cell_type": "markdown", + "id": "317fd474", + "metadata": {}, + "source": [ + "## Usage\n", + "\n", + "Initialize `GroundednessCheck` class." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b7373380c01cefbe", + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from langchain_upstage import GroundednessCheck\n", + "\n", + "groundedness_check = GroundednessCheck()" + ] + }, + { + "cell_type": "markdown", + "id": "f4a7f9450b3ef2c1", + "metadata": {}, + "source": [ + "Use the `run` method to check the groundedness of the input text." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1e0115e3b511f57", + "metadata": { + "collapsed": false, + "is_executing": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "content='notGrounded' response_metadata={'token_usage': {'completion_tokens': 6, 'prompt_tokens': 198, 'total_tokens': 204}, 'model_name': 'solar-1-mini-answer-verification', 'system_fingerprint': '', 'finish_reason': 'stop', 'logprobs': None} id='run-ce7b5787-2ed0-4a68-9de4-c0e91a824147-0'\n" + ] + } + ], + "source": [ + "request_input = {\n", + " \"context\": \"Mauna Kea is an inactive volcano on the island of Hawai'i. Its peak is 4,207.3 m above sea level, making it the highest point in Hawaii and second-highest peak of an island on Earth.\",\n", + " \"query\": \"Mauna Kea is 5,207.3 meters tall.\",\n", + "}\n", + "\n", + "response = groundedness_check.run(request_input)\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "054b5031", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/vercel_build.sh b/docs/vercel_build.sh index a3cd55e803..f570319b46 100755 --- a/docs/vercel_build.sh +++ b/docs/vercel_build.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + yum -y update yum install gcc bzip2-devel libffi-devel zlib-devel wget tar gzip -y diff --git a/libs/partners/upstage/langchain_upstage/__init__.py b/libs/partners/upstage/langchain_upstage/__init__.py index 431fe8d54c..9ecef2810d 100644 --- a/libs/partners/upstage/langchain_upstage/__init__.py +++ b/libs/partners/upstage/langchain_upstage/__init__.py @@ -1,4 +1,13 @@ from langchain_upstage.chat_models import ChatUpstage from langchain_upstage.embeddings import UpstageEmbeddings +from langchain_upstage.layout_analysis import UpstageLayoutAnalysisLoader +from langchain_upstage.layout_analysis_parsers import UpstageLayoutAnalysisParser +from langchain_upstage.tools.groundedness_check import GroundednessCheck -__all__ = ["ChatUpstage", "UpstageEmbeddings"] +__all__ = [ + "ChatUpstage", + "UpstageEmbeddings", + "UpstageLayoutAnalysisLoader", + "UpstageLayoutAnalysisParser", + "GroundednessCheck", +] diff --git a/libs/partners/upstage/langchain_upstage/layout_analysis.py b/libs/partners/upstage/langchain_upstage/layout_analysis.py new file mode 100644 index 0000000000..10ae40db67 --- /dev/null +++ b/libs/partners/upstage/langchain_upstage/layout_analysis.py @@ -0,0 +1,190 @@ +import os +from pathlib import Path +from typing import Iterator, List, Literal, Optional, Union + +from langchain_core.document_loaders import BaseLoader, Blob +from langchain_core.documents import Document + +from .layout_analysis_parsers import UpstageLayoutAnalysisParser + +DEFAULT_PAGE_BATCH_SIZE = 10 + +OutputType = Literal["text", "html"] +SplitType = Literal["none", "element", "page"] + + +def validate_api_key(api_key: str) -> None: + """ + Validates the provided API key. + + Args: + api_key (str): The API key to be validated. + + Raises: + ValueError: If the API key is empty or None. + + Returns: + None + """ + if not api_key: + raise ValueError("API Key is required for Upstage Document Loader") + + +def validate_file_path(file_path: Union[str, Path, List[str], List[Path]]) -> None: + """ + Validates if a file exists at the given file path. + + Args: + file_path (Union[str, Path, List[str], List[Path]): The file path(s) to be + validated. + + Raises: + FileNotFoundError: If the file or any of the files in the list do not exist. + """ + if isinstance(file_path, list): + for path in file_path: + validate_file_path(path) + return + if not os.path.exists(file_path): + raise FileNotFoundError(f"File not found: {file_path}") + + +def get_from_param_or_env( + key: str, + param: Optional[str] = None, + env_key: Optional[str] = None, + default: Optional[str] = None, +) -> str: + """Get a value from a param or an environment variable.""" + if param is not None: + return param + elif env_key and env_key in os.environ and os.environ[env_key]: + return os.environ[env_key] + elif default is not None: + return default + else: + raise ValueError( + f"Did not find {key}, please add an environment variable" + f" `{env_key}` which contains it, or pass" + f" `{key}` as a named parameter." + ) + + +class UpstageLayoutAnalysisLoader(BaseLoader): + """Upstage Layout Analysis. + + To use, you should have the environment variable `UPSTAGE_DOCUMENT_AI_API_KEY` + set with your API key or pass it as a named parameter to the constructor. + + Example: + .. code-block:: python + + from langchain_upstage import UpstageLayoutAnalysis + + file_path = "/PATH/TO/YOUR/FILE.pdf" + loader = UpstageLayoutAnalysis( + file_path, split="page", output_type="text" + ) + """ + + def __init__( + self, + file_path: Union[str, Path, List[str], List[Path]], + output_type: Union[OutputType, dict] = "text", + split: SplitType = "none", + api_key: Optional[str] = None, + use_ocr: bool = False, + ): + """ + Initializes an instance of the Upstage document loader. + + Args: + file_path (Union[str, Path, List[str], List[Path]): The path to the document + to be loaded. + output_type (Union[OutputType, dict], optional): The type of output to be + generated by the parser. + Defaults to "text". + split (SplitType, optional): The type of splitting to be applied. + Defaults to "none" (no splitting). + api_key (str, optional): The API key for accessing the Upstage API. + Defaults to None, in which case it will be + fetched from the environment variable + `UPSTAGE_DOCUMENT_AI_API_KEY`. + use_ocr (bool, optional): Extract text from images in the document. + Defaults to False. (Use text info in PDF file) + """ + self.file_path = file_path + self.output_type = output_type + self.split = split + self.api_key = get_from_param_or_env( + "UPSTAGE_DOCUMENT_AI_API_KEY", api_key, "UPSTAGE_DOCUMENT_AI_API_KEY" + ) + self.use_ocr = use_ocr + + validate_file_path(self.file_path) + validate_api_key(self.api_key) + + def load(self) -> List[Document]: + """ + Loads and parses the document using the UpstageLayoutAnalysisParser. + + Returns: + A list of Document objects representing the parsed layout analysis. + """ + + if isinstance(self.file_path, list): + result = [] + + for file_path in self.file_path: + blob = Blob.from_path(file_path) + + parser = UpstageLayoutAnalysisParser( + self.api_key, + split=self.split, + output_type=self.output_type, + use_ocr=self.use_ocr, + ) + result.extend(list(parser.lazy_parse(blob, is_batch=True))) + + return result + + else: + blob = Blob.from_path(self.file_path) + + parser = UpstageLayoutAnalysisParser( + self.api_key, + split=self.split, + output_type=self.output_type, + use_ocr=self.use_ocr, + ) + return list(parser.lazy_parse(blob, is_batch=True)) + + def lazy_load(self) -> Iterator[Document]: + """ + Lazily loads and parses the document using the UpstageLayoutAnalysisParser. + + Returns: + An iterator of Document objects representing the parsed layout analysis. + """ + + if isinstance(self.file_path, list): + for file_path in self.file_path: + blob = Blob.from_path(file_path) + + parser = UpstageLayoutAnalysisParser( + self.api_key, + split=self.split, + output_type=self.output_type, + use_ocr=self.use_ocr, + ) + yield from parser.lazy_parse(blob, is_batch=True) + else: + blob = Blob.from_path(self.file_path) + + parser = UpstageLayoutAnalysisParser( + self.api_key, + split=self.split, + output_type=self.output_type, + use_ocr=self.use_ocr, + ) + yield from parser.lazy_parse(blob) diff --git a/libs/partners/upstage/langchain_upstage/layout_analysis_parsers.py b/libs/partners/upstage/langchain_upstage/layout_analysis_parsers.py new file mode 100644 index 0000000000..d9ffb2783c --- /dev/null +++ b/libs/partners/upstage/langchain_upstage/layout_analysis_parsers.py @@ -0,0 +1,375 @@ +import io +import json +import os +from typing import Dict, Iterator, List, Literal, Optional, Union + +import fitz # type: ignore +import requests +from fitz import Document as fitzDocument +from langchain_core.document_loaders import BaseBlobParser, Blob +from langchain_core.documents import Document + +LAYOUT_ANALYSIS_URL = "https://api.upstage.ai/v1/document-ai/layout-analysis" + +DEFAULT_NUMBER_OF_PAGE = 10 + +OutputType = Literal["text", "html"] +SplitType = Literal["none", "element", "page"] + + +def validate_api_key(api_key: str) -> None: + """ + Validates the provided API key. + + Args: + api_key (str): The API key to be validated. + + Raises: + ValueError: If the API key is empty or None. + + Returns: + None + """ + if not api_key: + raise ValueError("API Key is required for Upstage Document Loader") + + +def validate_file_path(file_path: str) -> None: + """ + Validates if a file exists at the given file path. + + Args: + file_path (str): The path to the file. + + Raises: + FileNotFoundError: If the file does not exist at the given file path. + """ + if not os.path.exists(file_path): + raise FileNotFoundError(f"File not found: {file_path}") + + +def parse_output(data: dict, output_type: Union[OutputType, dict]) -> str: + """ + Parse the output data based on the specified output type. + + Args: + data (dict): The data to be parsed. + output_type (Union[OutputType, dict]): The output type to parse the element data + into. + + Returns: + str: The parsed output. + + Raises: + ValueError: If the output type is invalid. + """ + if isinstance(output_type, dict): + if data["category"] in output_type: + return data[output_type[data["category"]]] + else: + return data["text"] + elif isinstance(output_type, str): + if output_type == "text": + return data["text"] + elif output_type == "html": + return data["html"] + else: + raise ValueError(f"Invalid output type: {output_type}") + else: + raise ValueError(f"Invalid output type: {output_type}") + + +def get_from_param_or_env( + key: str, + param: Optional[str] = None, + env_key: Optional[str] = None, + default: Optional[str] = None, +) -> str: + """Get a value from a param or an environment variable.""" + if param is not None: + return param + elif env_key and env_key in os.environ and os.environ[env_key]: + return os.environ[env_key] + elif default is not None: + return default + else: + raise ValueError( + f"Did not find {key}, please add an environment variable" + f" `{env_key}` which contains it, or pass" + f" `{key}` as a named parameter." + ) + + +class UpstageLayoutAnalysisParser(BaseBlobParser): + """Upstage Layout Analysis Parser. + + To use, you should have the environment variable `UPSTAGE_DOCUMENT_AI_API_KEY` + set with your API key or pass it as a named parameter to the constructor. + + Example: + .. code-block:: python + + from langchain_upstage import UpstageLayoutAnalysisParser + + loader = UpstageLayoutAnalysisParser(split="page", output_type="text") + """ + + def __init__( + self, + api_key: Optional[str] = None, + output_type: Union[OutputType, dict] = "text", + split: SplitType = "none", + use_ocr: bool = False, + ): + """ + Initializes an instance of the Upstage class. + + Args: + api_key (str, optional): The API key for accessing the Upstage API. + Defaults to None, in which case it will be + fetched from the environment variable + `UPSTAGE_DOCUMENT_AI_API_KEY`. + output_type (Union[OutputType, dict], optional): The type of output to be + generated by the parser. + Defaults to "text". + split (SplitType, optional): The type of splitting to be applied. + Defaults to "none" (no splitting). + use_ocr (bool, optional): Extract text from images in the document. + Defaults to False. (Use text info in PDF file) + """ + self.api_key = get_from_param_or_env( + "UPSTAGE_DOCUMENT_AI_API_KEY", api_key, "UPSTAGE_DOCUMENT_AI_API_KEY" + ) + + self.output_type = output_type + self.split = split + self.use_ocr = use_ocr + + validate_api_key(self.api_key) + + def _get_response(self, files: Dict) -> Dict: + """ + Sends a POST request to the API endpoint with the provided files and + returns the response. + + Args: + files (dict): A dictionary containing the files to be sent in the request. + + Returns: + dict: The JSON response from the API. + + Raises: + ValueError: If there is an error in the API call. + """ + try: + headers = {"Authorization": f"Bearer {self.api_key}"} + options = {"ocr": self.use_ocr} + response = requests.post( + LAYOUT_ANALYSIS_URL, headers=headers, files=files, json=options + ) + response.raise_for_status() + + result = response.json() + + except requests.RequestException as req_err: + # Handle any request-related exceptions + print(f"Request Exception: {req_err}") + except json.JSONDecodeError as json_err: + # Handle JSON decode errors + print(f"JSON Decode Error: {json_err}") + raise ValueError(f"Failed to decode JSON response: {json_err}") + + return result + + def _split_and_request( + self, + full_docs: fitzDocument, + start_page: int, + num_pages: int = DEFAULT_NUMBER_OF_PAGE, + ) -> Dict: + """ + Splits the full pdf document into partial pages and sends a request to the + server. + + Args: + full_docs (str): The full document to be split and requested. + start_page (int): The starting page number for splitting the document. + num_pages (int, optional): The number of pages to split the document + into. + Defaults to DEFAULT_NUMBER_OF_PAGE. + + Returns: + response: The response from the server. + """ + with fitz.open() as chunk_pdf: + chunk_pdf.insert_pdf( + full_docs, + from_page=start_page, + to_page=start_page + num_pages - 1, + ) + pdf_bytes = chunk_pdf.write() + + with io.BytesIO(pdf_bytes) as f: + response = self._get_response({"document": f}) + + return response + + def _element_document(self, elements: Dict) -> Document: + """ + Converts an elements into a Document object. + + Args: + elements: The elements to convert. + + Returns: + A list containing a single Document object. + + """ + return Document( + page_content=(parse_output(elements, self.output_type)), + metadata={ + "page": elements["page"], + "id": elements["id"], + "type": self.output_type, + "split": self.split, + }, + ) + + def _page_document(self, elements: Dict) -> List[Document]: + """ + Combines elements with the same page number into a single Document object. + + Args: + elements (List[Dict]): A list of elements containing page numbers. + + Returns: + List[Document]: A list of Document objects, each representing a page + with its content and metadata. + """ + _docs = [] + pages = sorted(set(map(lambda x: x["page"], elements))) + + page_group = [ + [element for element in elements if element["page"] == x] for x in pages + ] + + for group in page_group: + page_content = " ".join( + [parse_output(element, self.output_type) for element in group] + ) + + _docs.append( + Document( + page_content=page_content, + metadata={ + "page": group[0]["page"], + "type": self.output_type, + "split": self.split, + }, + ) + ) + + return _docs + + def lazy_parse(self, blob: Blob, is_batch: bool = False) -> Iterator[Document]: + """ + Lazily parses a document and yields Document objects based on the specified + split type. + + Args: + blob (Blob): The input document blob to parse. + is_batch (bool, optional): Whether to parse the document in batches. + Defaults to False (single page parsing) + + Yields: + Document: The parsed document object. + + Raises: + ValueError: If an invalid split type is provided. + + """ + + if is_batch: + num_pages = DEFAULT_NUMBER_OF_PAGE + else: + num_pages = 1 + + full_docs = fitz.open(blob.path) + number_of_pages = full_docs.page_count + + if self.split == "none": + if full_docs.is_pdf: + result = "" + start_page = 0 + num_pages = DEFAULT_NUMBER_OF_PAGE + for _ in range(number_of_pages): + if start_page >= number_of_pages: + break + + response = self._split_and_request(full_docs, start_page, num_pages) + result += parse_output(response, self.output_type) + + start_page += num_pages + + else: + if not blob.path: + raise ValueError("Blob path is required for non-PDF files.") + with open(blob.path, "rb") as f: + response = self._get_response({"document": f}) + result = parse_output(response, self.output_type) + + yield Document( + page_content=result, + metadata={ + "total_pages": number_of_pages, + "type": self.output_type, + "split": self.split, + }, + ) + + elif self.split == "element": + if full_docs.is_pdf: + start_page = 0 + for _ in range(number_of_pages): + if start_page >= number_of_pages: + break + + response = self._split_and_request(full_docs, start_page, num_pages) + for element in response["elements"]: + yield self._element_document(element) + + start_page += num_pages + + else: + if not blob.path: + raise ValueError("Blob path is required for non-PDF files.") + with open(blob.path, "rb") as f: + response = self._get_response({"document": f}) + + for element in response["elements"]: + yield self._element_document(element) + + elif self.split == "page": + if full_docs.is_pdf: + start_page = 0 + for _ in range(number_of_pages): + if start_page >= number_of_pages: + break + + response = self._split_and_request(full_docs, start_page, num_pages) + elements = response["elements"] + yield from self._page_document(elements) + + start_page += num_pages + else: + if not blob.path: + raise ValueError("Blob path is required for non-PDF files.") + with open(blob.path, "rb") as f: + response = self._get_response({"document": f}) + + elements = response["elements"] + + yield from self._page_document(elements) + + else: + raise ValueError(f"Invalid split type: {self.split}") diff --git a/libs/partners/upstage/langchain_upstage/tools/groundedness_check.py b/libs/partners/upstage/langchain_upstage/tools/groundedness_check.py new file mode 100644 index 0000000000..d423e7aa43 --- /dev/null +++ b/libs/partners/upstage/langchain_upstage/tools/groundedness_check.py @@ -0,0 +1,91 @@ +import os +from typing import Literal, Optional, Type, Union + +from langchain_core.callbacks import ( + AsyncCallbackManagerForToolRun, + CallbackManagerForToolRun, +) +from langchain_core.messages import AIMessage, HumanMessage +from langchain_core.pydantic_v1 import BaseModel, Field, SecretStr +from langchain_core.tools import BaseTool + +from langchain_upstage import ChatUpstage + + +class GroundednessCheckInput(BaseModel): + """Input for the Groundedness Check tool.""" + + context: str = Field(description="context in which the answer should be verified") + query: str = Field( + description="assistant's reply or a text that is subject to groundedness check" + ) + + +class GroundednessCheck(BaseTool): + """Tool that checks the groundedness of a context and an assistant message. + + To use, you should have the environment variable `UPSTAGE_API_KEY` + set with your API key or pass it as a named parameter to the constructor. + + Example: + .. code-block:: python + + from langchain_upstage import GroundednessCheck + + tool = GroundednessCheck() + """ + + name: str = "groundedness_check" + description: str = ( + "A tool that checks the groundedness of an assistant response " + "to user-provided context. GroundednessCheck ensures that " + "the assistant’s response is not only relevant but also " + "precisely aligned with the user's initial context, " + "promoting a more reliable and context-aware interaction. " + "When using retrieval-augmented generation (RAG), " + "the Groundedness Check can be used to determine whether " + "the assistant's message is grounded in the provided context." + ) + upstage_api_key: Optional[SecretStr] = Field(default=None, alias="api_key") + api_wrapper: ChatUpstage + + args_schema: Type[BaseModel] = GroundednessCheckInput + + def __init__(self, upstage_api_key: Optional[SecretStr] = None): + if not upstage_api_key: + upstage_api_key = SecretStr(os.getenv("UPSTAGE_API_KEY", "")) + else: + upstage_api_key = upstage_api_key + if ( + not upstage_api_key + or not upstage_api_key.get_secret_value() + or upstage_api_key.get_secret_value() == "" + ): + raise ValueError("UPSTAGE_API_KEY must be set or passed") + + api_wrapper = ChatUpstage( + model_name="solar-1-mini-answer-verification", + upstage_api_key=upstage_api_key.get_secret_value(), + ) + super().__init__(upstage_api_key=upstage_api_key, api_wrapper=api_wrapper) + + def _run( + self, + context: str, + query: str, + run_manager: Optional[CallbackManagerForToolRun] = None, + ) -> Union[str, Literal["grounded", "notGrounded", "notSure"]]: + """Use the tool.""" + response = self.api_wrapper.invoke([HumanMessage(context), AIMessage(query)]) + return str(response.content) + + async def _arun( + self, + context: str, + query: str, + run_manager: Optional[AsyncCallbackManagerForToolRun] = None, + ) -> Union[str, Literal["grounded", "notGrounded", "notSure"]]: + response = await self.api_wrapper.ainvoke( + [HumanMessage(context), AIMessage(query)] + ) + return str(response.content) diff --git a/libs/partners/upstage/poetry.lock b/libs/partners/upstage/poetry.lock index 2339eb2f8f..ca3b46ec55 100644 --- a/libs/partners/upstage/poetry.lock +++ b/libs/partners/upstage/poetry.lock @@ -223,13 +223,13 @@ test = ["pytest (>=6)"] [[package]] name = "freezegun" -version = "1.4.0" +version = "1.5.0" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" files = [ - {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"}, - {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"}, + {file = "freezegun-1.5.0-py3-none-any.whl", hash = "sha256:ec3f4ba030e34eb6cf7e1e257308aee2c60c3d038ff35996d7475760c9ff3719"}, + {file = "freezegun-1.5.0.tar.gz", hash = "sha256:200a64359b363aa3653d8aac289584078386c7c3da77339d257e46a01fb5c77c"}, ] [package.dependencies] @@ -340,7 +340,7 @@ files = [ [[package]] name = "langchain-core" -version = "0.1.44" +version = "0.1.45" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" @@ -399,13 +399,13 @@ url = "../../standard-tests" [[package]] name = "langsmith" -version = "0.1.49" +version = "0.1.50" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langsmith-0.1.49-py3-none-any.whl", hash = "sha256:cf0db7474c0dfb22015c22bf97f62e850898c3c6af9564dd111c2df225acc1c8"}, - {file = "langsmith-0.1.49.tar.gz", hash = "sha256:5aee8537763f9d62b3368d79d7bfef881e2bfaa28639011d8d7328770cbd6419"}, + {file = "langsmith-0.1.50-py3-none-any.whl", hash = "sha256:a81e9809fcaa277bfb314d729e58116554f186d1478fcfdf553b1c2ccce54b85"}, + {file = "langsmith-0.1.50.tar.gz", hash = "sha256:9fd22df8c689c044058536ea5af66f5302067e7551b60d7a335fede8d479572b"}, ] [package.dependencies] @@ -548,13 +548,13 @@ files = [ [[package]] name = "openai" -version = "1.23.1" +version = "1.23.3" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.23.1-py3-none-any.whl", hash = "sha256:7941c1bc6fcdb1b6b889dfcfabff775ca52558a79d57dd1b9e15b463de1b3a4c"}, - {file = "openai-1.23.1.tar.gz", hash = "sha256:6df937e2a1ad64494951ea3614f5516db4d67c3fcc0b751b8e5edf1bc57e2d3d"}, + {file = "openai-1.23.3-py3-none-any.whl", hash = "sha256:6eef764a8870095d256d59e6be243acf560a21227e9e3588b508972818929ef7"}, + {file = "openai-1.23.3.tar.gz", hash = "sha256:6730b8468a0235e5f289dfdfacaa374001645099c4ad1740b58eab378bcf7723"}, ] [package.dependencies] @@ -642,13 +642,13 @@ files = [ [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -722,6 +722,64 @@ files = [ plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pymupdf" +version = "1.24.2" +description = "A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents." +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyMuPDF-1.24.2-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:5faed2bbdfbea80db1bbaa5944888f27a672f2e10e16e61f7d4ff73429a00506"}, + {file = "PyMuPDF-1.24.2-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:24c398e43a14e0e11f3515ea57875b5b0ee1a37d6dc59f921f69d8d16e881cb8"}, + {file = "PyMuPDF-1.24.2-cp310-none-manylinux2014_aarch64.whl", hash = "sha256:569336fe3c5f81f28aa9658861597e43e5716cbfa5b8d2602431095df76e0d7c"}, + {file = "PyMuPDF-1.24.2-cp310-none-manylinux2014_x86_64.whl", hash = "sha256:8fe58a024629c23847423b3294f0f160c72c72f953af53d183bd3328f954593a"}, + {file = "PyMuPDF-1.24.2-cp310-none-win32.whl", hash = "sha256:49224a558736303ed980252a704646fe9347c74bf70d0ad32530c62b8e0bfe33"}, + {file = "PyMuPDF-1.24.2-cp310-none-win_amd64.whl", hash = "sha256:a32c94c7ee45f2bfee766e5b957bdfe08c96b21fd9adbfb546c141621af0ca85"}, + {file = "PyMuPDF-1.24.2-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:815d9e10faa43a149d8c9928d7cefda83fd91a1f637dfb3a295620175a0af95c"}, + {file = "PyMuPDF-1.24.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:b583add37141a9337935d014d4e1913b10e22d17f3fd656fdc5f0c0c2e65a33e"}, + {file = "PyMuPDF-1.24.2-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:d6a4d4ad8cc698db25a31026311f03fd351c2db9bfd41d898494cd0baff3b679"}, + {file = "PyMuPDF-1.24.2-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:7b5acb936203bdaef5945f211af8a5fb40f07059be1ba69a728425f6d522e60f"}, + {file = "PyMuPDF-1.24.2-cp311-none-win32.whl", hash = "sha256:d01d348a35438f8a1647334428ef23c6d947acae875fa61cac2be3a65b15e4f5"}, + {file = "PyMuPDF-1.24.2-cp311-none-win_amd64.whl", hash = "sha256:909ab62c752be80c3c130a9774fc27fb863d26149ba880129e0a2cf0d53cebde"}, + {file = "PyMuPDF-1.24.2-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:6a3c1f2e99a4ca43c97b1f43fdd1aed739910e25ee5bd7fe73cd4eaf59841ae3"}, + {file = "PyMuPDF-1.24.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:3effff62943ceebbbe32a08ce4aa9c8ed4fa18fd8a462cf6130c78818c47822d"}, + {file = "PyMuPDF-1.24.2-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:f3964783bf81f2ec94db4f9fa536052be3b7457824c9e9d21edb91f3a489a377"}, + {file = "PyMuPDF-1.24.2-cp312-none-manylinux2014_x86_64.whl", hash = "sha256:04af266755d4c250b46a3311062aec36ea94cecc4470a53ab79d9de56e5a753d"}, + {file = "PyMuPDF-1.24.2-cp312-none-win32.whl", hash = "sha256:3bd7bdda4c4e4f98989ce84a7b2c08033639a8be1b46fb064fdd65b20a7e7d03"}, + {file = "PyMuPDF-1.24.2-cp312-none-win_amd64.whl", hash = "sha256:ec2544f35088b29730210decfb0bdb750e0c3d2652ee470897f6d2e4a6dc1950"}, + {file = "PyMuPDF-1.24.2-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:d4fd3957fd507affbcae4536092cb3e3726e91d484be16972603c5cacae7848a"}, + {file = "PyMuPDF-1.24.2-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:4290273dfcc58a2c0b1f207f5e25479b868f59e9ea6ac9241740506fa0c03c0a"}, + {file = "PyMuPDF-1.24.2-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:8f52f27d1f5968b6dda4d803e7f5246626a45ab68f0626509a9e17fadcebfb69"}, + {file = "PyMuPDF-1.24.2-cp38-none-manylinux2014_x86_64.whl", hash = "sha256:db650840eb3efbdc97df94210d0400042c863b08348d67037495d221ec4e8b7f"}, + {file = "PyMuPDF-1.24.2-cp38-none-win32.whl", hash = "sha256:423217223741f55f9bb7622475a94c2934495e8a843246c582c78f3680914a80"}, + {file = "PyMuPDF-1.24.2-cp38-none-win_amd64.whl", hash = "sha256:ca493fbb91d81a43d68d3547194d0c86083db49d4dd98e8f41aa5a77a26ff8fe"}, + {file = "PyMuPDF-1.24.2-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:9783b67f63e7f9b397f119de996ea8214498d163531b9371d8ea7e374cdd45cd"}, + {file = "PyMuPDF-1.24.2-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:4db161926d636c0bff016ac7591edbe6b30712507079f7008cefb0fdf58055dc"}, + {file = "PyMuPDF-1.24.2-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:537cc7bef86514a6fa248eeb14b588f51699388628372cf31bae7839283aa628"}, + {file = "PyMuPDF-1.24.2-cp39-none-manylinux2014_x86_64.whl", hash = "sha256:a124b360898d24b730fe3be0e0bca438789c1568ceaad854387eee1886bb788c"}, + {file = "PyMuPDF-1.24.2-cp39-none-win32.whl", hash = "sha256:007586883fbc8acb900d46aa95520aaeb8943d05a956b26c54053ddb58dbdd5f"}, + {file = "PyMuPDF-1.24.2-cp39-none-win_amd64.whl", hash = "sha256:d89cbb1a093dbf042f503f5c7fc368d0718a652418512a7a42a2965cba27713d"}, + {file = "PyMuPDF-1.24.2.tar.gz", hash = "sha256:cdaca48b7677a0c1dc827413b90c8fe4517f789f74c6ac0fb47f6051368246bb"}, +] + +[package.dependencies] +PyMuPDFb = "1.24.1" + +[[package]] +name = "pymupdfb" +version = "1.24.1" +description = "MuPDF shared libraries for PyMuPDF." +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyMuPDFb-1.24.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:37179e363bf69ce9be637937c5469957b96968341dabe3ce8f4b690a82e9ad92"}, + {file = "PyMuPDFb-1.24.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:17444ea7d6897c27759880ad76af537d19779f901de82ae9548598a70f614558"}, + {file = "PyMuPDFb-1.24.1-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:490f7fff4dbe362bc895cefdfc5030d712311d024d357a1388d64816eb215d34"}, + {file = "PyMuPDFb-1.24.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0fbcc0d2a9ce79fa38eb4e8bb5c959b582f7a49938874e9f61d1a6f5eeb1e4b8"}, + {file = "PyMuPDFb-1.24.1-py3-none-win32.whl", hash = "sha256:ae67736058882cdd9459810a4aae9ac2b2e89ac2e916cb5fefb0f651c9739e9e"}, + {file = "PyMuPDFb-1.24.1-py3-none-win_amd64.whl", hash = "sha256:01c8b7f0ce9166310eb28c7aebcb8d5fe12a4bc082f9b00d580095eebeaf0af5"}, +] + [[package]] name = "pytest" version = "7.4.4" @@ -1270,4 +1328,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4.0" -content-hash = "98a8d67be9138240d5190eb4774b93f671fbd8069839ad239d005c753bdbae0d" +content-hash = "1bb654e8a4f60cca5f0562ade5477a2f2e852ed2a361c7e9162208fbeb445309" diff --git a/libs/partners/upstage/pyproject.toml b/libs/partners/upstage/pyproject.toml index 4a947c9238..d07f6bb167 100644 --- a/libs/partners/upstage/pyproject.toml +++ b/libs/partners/upstage/pyproject.toml @@ -14,6 +14,8 @@ license = "MIT" python = ">=3.8.1,<4.0" langchain-core = "^0.1.44" langchain-openai = "^0.1.3" +pymupdf = "^1.24.1" +requests = "^2.31.0" [tool.poetry.group.test] optional = true @@ -50,6 +52,7 @@ ruff = "^0.1.5" [tool.poetry.group.typing.dependencies] mypy = "^0.991" +types-requests = ">=2.31.0" langchain-core = { path = "../../core", develop = true } [tool.poetry.group.dev] diff --git a/libs/partners/upstage/tests/examples/solar.pdf b/libs/partners/upstage/tests/examples/solar.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e5266aa483af3ba3ac8d1c989990ee9525315065 GIT binary patch literal 111524 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 None: + """Test Upstage Groundedness Check.""" + tool = GroundednessCheck() + output = tool.run({"context": "foo bar", "query": "bar foo"}) + + assert output in ["grounded", "notGrounded", "notSure"] + + +async def test_langchain_upstage_groundedness_check_async() -> None: + """Test Upstage Groundedness Check asynchronous.""" + tool = GroundednessCheck() + output = await tool.arun({"context": "foo bar", "query": "bar foo"}) + + assert output in ["grounded", "notGrounded", "notSure"] diff --git a/libs/partners/upstage/tests/unit_tests/test_groundedness_check.py b/libs/partners/upstage/tests/unit_tests/test_groundedness_check.py new file mode 100644 index 0000000000..cba5f1bd9e --- /dev/null +++ b/libs/partners/upstage/tests/unit_tests/test_groundedness_check.py @@ -0,0 +1,10 @@ +import os + +from langchain_upstage import GroundednessCheck + +os.environ["UPSTAGE_API_KEY"] = "foo" + + +def test_initialization() -> None: + """Test embedding model initialization.""" + GroundednessCheck() diff --git a/libs/partners/upstage/tests/unit_tests/test_imports.py b/libs/partners/upstage/tests/unit_tests/test_imports.py index e11947fa18..7fe6498700 100644 --- a/libs/partners/upstage/tests/unit_tests/test_imports.py +++ b/libs/partners/upstage/tests/unit_tests/test_imports.py @@ -3,6 +3,9 @@ from langchain_upstage import __all__ EXPECTED_ALL = [ "ChatUpstage", "UpstageEmbeddings", + "UpstageLayoutAnalysisLoader", + "UpstageLayoutAnalysisParser", + "GroundednessCheck", ] diff --git a/libs/partners/upstage/tests/unit_tests/test_layout_analysis.py b/libs/partners/upstage/tests/unit_tests/test_layout_analysis.py new file mode 100644 index 0000000000..646c34b87d --- /dev/null +++ b/libs/partners/upstage/tests/unit_tests/test_layout_analysis.py @@ -0,0 +1,200 @@ +from pathlib import Path +from typing import Any, Dict, get_args +from unittest.mock import MagicMock, Mock, patch + +from langchain_upstage import UpstageLayoutAnalysisLoader +from langchain_upstage.layout_analysis import OutputType, SplitType + +MOCK_RESPONSE_JSON: Dict[str, Any] = { + "api": "1.0", + "billed_pages": 1, + "elements": [ + { + "bounding_box": [ + {"x": 74, "y": 906}, + {"x": 148, "y": 906}, + {"x": 148, "y": 2338}, + {"x": 74, "y": 2338}, + ], + "category": "header", + "html": "2021arXiv:2103.15348v2", + "id": 0, + "page": 1, + "text": "arXiv:2103.15348v2", + }, + { + "bounding_box": [ + {"x": 654, "y": 474}, + {"x": 1912, "y": 474}, + {"x": 1912, "y": 614}, + {"x": 654, "y": 614}, + ], + "category": "paragraph", + "html": "LayoutParser Toolkit", + "id": 1, + "page": 1, + "text": "LayoutParser Toolkit", + }, + ], + "html": "
arXiv:2103.15348v2
" + + "

LayoutParser Toolkit

", + "mimetype": "multipart/form-data", + "model": "layout-analyzer-0.1.0", + "text": "arXiv:2103.15348v2LayoutParser Toolkit", +} + +EXAMPLE_PDF_PATH = Path(__file__).parent.parent / "examples/solar.pdf" + + +def test_initialization() -> None: + """Test layout analysis document loader initialization.""" + UpstageLayoutAnalysisLoader(file_path=EXAMPLE_PDF_PATH, api_key="bar") + + +def test_layout_analysis_param() -> None: + for output_type in get_args(OutputType): + for split in get_args(SplitType): + loader = UpstageLayoutAnalysisLoader( + file_path=EXAMPLE_PDF_PATH, + api_key="bar", + output_type=output_type, + split=split, + ) + assert loader.output_type == output_type + assert loader.split == split + assert loader.api_key == "bar" + assert loader.file_path == EXAMPLE_PDF_PATH + + +@patch("requests.post") +def test_none_split_text_output(mock_post: Mock) -> None: + mock_post.return_value = MagicMock( + status_code=200, json=MagicMock(return_value=MOCK_RESPONSE_JSON) + ) + + loader = UpstageLayoutAnalysisLoader( + file_path=EXAMPLE_PDF_PATH, + output_type="text", + split="none", + api_key="valid_api_key", + ) + documents = loader.load() + + assert len(documents) == 1 + assert documents[0].page_content == MOCK_RESPONSE_JSON["text"] + assert documents[0].metadata["total_pages"] == 1 + assert documents[0].metadata["type"] == "text" + assert documents[0].metadata["split"] == "none" + + +@patch("requests.post") +def test_element_split_text_output(mock_post: Mock) -> None: + mock_post.return_value = MagicMock( + status_code=200, json=MagicMock(return_value=MOCK_RESPONSE_JSON) + ) + + loader = UpstageLayoutAnalysisLoader( + file_path=EXAMPLE_PDF_PATH, + output_type="text", + split="element", + api_key="valid_api_key", + ) + documents = loader.load() + + assert len(documents) == 2 + + for i, document in enumerate(documents): + assert document.page_content == MOCK_RESPONSE_JSON["elements"][i]["text"] + assert document.metadata["page"] == MOCK_RESPONSE_JSON["elements"][i]["page"] + assert document.metadata["id"] == MOCK_RESPONSE_JSON["elements"][i]["id"] + assert document.metadata["type"] == "text" + assert document.metadata["split"] == "element" + + +@patch("requests.post") +def test_page_split_text_output(mock_post: Mock) -> None: + mock_post.return_value = MagicMock( + status_code=200, json=MagicMock(return_value=MOCK_RESPONSE_JSON) + ) + + loader = UpstageLayoutAnalysisLoader( + file_path=EXAMPLE_PDF_PATH, + output_type="text", + split="page", + api_key="valid_api_key", + ) + documents = loader.load() + + assert len(documents) == 1 + + for i, document in enumerate(documents): + assert document.metadata["page"] == MOCK_RESPONSE_JSON["elements"][i]["page"] + assert document.metadata["type"] == "text" + assert document.metadata["split"] == "page" + + +@patch("requests.post") +def test_none_split_html_output(mock_post: Mock) -> None: + mock_post.return_value = MagicMock( + status_code=200, json=MagicMock(return_value=MOCK_RESPONSE_JSON) + ) + + loader = UpstageLayoutAnalysisLoader( + file_path=EXAMPLE_PDF_PATH, + output_type="html", + split="none", + api_key="valid_api_key", + ) + documents = loader.load() + + assert len(documents) == 1 + assert documents[0].page_content == MOCK_RESPONSE_JSON["html"] + assert documents[0].metadata["total_pages"] == 1 + assert documents[0].metadata["type"] == "html" + assert documents[0].metadata["split"] == "none" + + +@patch("requests.post") +def test_element_split_html_output(mock_post: Mock) -> None: + mock_post.return_value = MagicMock( + status_code=200, json=MagicMock(return_value=MOCK_RESPONSE_JSON) + ) + + loader = UpstageLayoutAnalysisLoader( + file_path=EXAMPLE_PDF_PATH, + output_type="html", + split="element", + api_key="valid_api_key", + ) + documents = loader.load() + + assert len(documents) == 2 + + for i, document in enumerate(documents): + assert document.page_content == MOCK_RESPONSE_JSON["elements"][i]["html"] + assert document.metadata["page"] == MOCK_RESPONSE_JSON["elements"][i]["page"] + assert document.metadata["id"] == MOCK_RESPONSE_JSON["elements"][i]["id"] + assert document.metadata["type"] == "html" + assert document.metadata["split"] == "element" + + +@patch("requests.post") +def test_page_split_html_output(mock_post: Mock) -> None: + mock_post.return_value = MagicMock( + status_code=200, json=MagicMock(return_value=MOCK_RESPONSE_JSON) + ) + + loader = UpstageLayoutAnalysisLoader( + file_path=EXAMPLE_PDF_PATH, + output_type="html", + split="page", + api_key="valid_api_key", + ) + documents = loader.load() + + assert len(documents) == 1 + + for i, document in enumerate(documents): + assert document.metadata["page"] == MOCK_RESPONSE_JSON["elements"][i]["page"] + assert document.metadata["type"] == "html" + assert document.metadata["split"] == "page"