Spaces:
Sleeping
Sleeping
Download app.py from juniorjukeko/small-pdf-summarizer: direct link, hf CLI and curl.
- Browser
- Download file 9.07 kB
-
https://huggingface.co/spaces/juniorjukeko/small-pdf-summarizer/resolve/main/app.py
- Command line
-
hf download hf://spaces/juniorjukeko/small-pdf-summarizer/app.py
-
curl -L -o app.py https://huggingface.co/spaces/juniorjukeko/small-pdf-summarizer/resolve/main/app.py
9.07 kB
| import os | |
| import openai | |
| from pypdf import PdfReader | |
| from langchain_community.document_loaders import PyPDFLoader | |
| from langchain_core.documents import Document | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| import gradio as gr | |
| title = ''' | |
| <div style="text-align: left; font-family:Arial; color:Black; font-size: 16px; max-width: 750px;"> | |
| <h1>Small PDF Summarizer</h1> | |
| <p style="text-align: left;">This App can be used to summarize small PDF (max. 1 MB, 15 pages)<br/> | |
| How to Use:<br/> | |
| 1. Upload a .PDF from your computer and fill OpenAI API key.<br/> | |
| 2. Click the "Upload PDF" button, if successful a preview of your PDF text will be shown.<br/> | |
| 3. Click "Summarize!" and the output will be shown on the textbox below.<br/> | |
| You can also change some LLM configurations from the 'config' tab.<br/> | |
| </div> | |
| ''' | |
| desc_1 = ''' | |
| <div style="text-align: left; font-family:Arial; color:Black; font-size: 14px;"> | |
| <h3>Custom Prompt Template</h3> | |
| <p style="text-align: left;">You can customize input prompt for the map and combine prompt of the map-reduce summarization pipeline | |
| using the textbox below.<br/> | |
| Prompt which will be fed into LLM uses the format: <b>{textbox input} + {pdf_text} + "SUMMARY:"</b> <br/> | |
| In essence each page of PDF will be summarized using the map prompt, and each summary then be combined for final output using combine prompt.<br/> | |
| <a href="https://python.langchain.com/docs/use_cases/summarization">More Info on Map-Reduce for Summarization</a> | |
| </div> | |
| ''' | |
| MAP_PROMPT = """ | |
| You will be given a page of text which section is enclosed in triple backticks (```). | |
| Your goal is to give a summary of this section, ignoring references and footnote if present. | |
| Your response should be at least 200 words only if input classified as academic text. | |
| Your response must fully encompass what was said in the page. | |
| ```{text}``` | |
| SUMMARY: | |
| """ | |
| COMBINE_PROMPT = """ | |
| Write a full summary of the following text enclosed in triple backticks (```). | |
| Full summary consists of a descriptive summary of at least 100 words (if possible), | |
| followed by numbered list which covers key points of the text. | |
| ```{text}``` | |
| SUMMARY: | |
| """ | |
| config_info = {'temperature': 'Higher means more randomness to the output.', | |
| 'max_tokens' : 'The maximum number of tokens to generate in the output.', | |
| 'llm_list' : ''} | |
| model_list = {'gpt-3.5-turbo':'chat', | |
| 'gpt-4':'chat', | |
| 'gpt-3.5-turbo-instruct':'instruct', | |
| 'text-davinci-003':'instruct'} | |
| text_splitter = RecursiveCharacterTextSplitter(separators=["\n\n", "\n"], chunk_size=10000, chunk_overlap=250) | |
| # globals to hold parsed PDF | |
| pdf_docs = [] | |
| page_count = 0 | |
| def parse_pdf(pdf_file): | |
| global pdf_docs, page_count | |
| if pdf_file is None: | |
| raise gr.Error("Please upload a PDF file.") | |
| loader = PyPDFLoader(pdf_file.name) | |
| pdf_docs = loader.load_and_split(text_splitter) | |
| page_count = len(pdf_docs) | |
| file_check(pdf_file) # will raise gr.Error on invalid | |
| # show first 200 chars preview | |
| return pdf_docs[0].page_content[:200] if page_count > 0 else "" | |
| def file_check(pdf_file): | |
| global page_count | |
| size_mb = os.path.getsize(pdf_file.name) / (1024 ** 2) | |
| if size_mb > 1: | |
| raise gr.Error("Maximum File Size is 1MB!") | |
| if page_count > 15: | |
| raise gr.Error("Maximum File Length is 15 Pages!") | |
| def generate_template(custom_prompt): | |
| # ensure {text} placeholder remains for substitution | |
| custom_template = custom_prompt + ''' | |
| ```{text}``` | |
| SUMMARY: | |
| ''' | |
| return custom_template | |
| def _call_openai_chat(model, prompt, temperature, max_tokens): | |
| # Chat model path | |
| resp = openai.ChatCompletion.create( | |
| model=model, | |
| messages=[{"role": "user", "content": prompt}], | |
| temperature=float(temperature), | |
| max_tokens=int(max_tokens) | |
| ) | |
| return resp["choices"][0]["message"]["content"].strip() | |
| def _call_openai_completion(model, prompt, temperature, max_tokens): | |
| # Completion (instruct) model path | |
| resp = openai.Completion.create( | |
| model=model, | |
| prompt=prompt, | |
| temperature=float(temperature), | |
| max_tokens=int(max_tokens), | |
| n=1, | |
| stop=None | |
| ) | |
| return resp["choices"][0]["text"].strip() | |
| def summarize_pdf(api_key, model_name, temperature, llm_max_tokens, custom_map_prompt, custom_combine_prompt): | |
| global pdf_docs | |
| if not pdf_docs: | |
| raise gr.Error("No PDF File Detected! Please upload a PDF first and click Upload.") | |
| # set API key | |
| openai.api_key = api_key | |
| if not api_key: | |
| raise gr.Error("OpenAI API key is required.") | |
| model_type = model_list.get(model_name, "chat") | |
| map_template = generate_template(custom_map_prompt) if custom_map_prompt else MAP_PROMPT | |
| combine_template = generate_template(custom_combine_prompt) if custom_combine_prompt else COMBINE_PROMPT | |
| map_summaries = [] | |
| try: | |
| # MAP step: summarize each page/chunk | |
| for idx, doc in enumerate(pdf_docs, start=1): | |
| text = doc.page_content | |
| prompt = map_template.replace("{text}", text) | |
| if model_type == "chat": | |
| summary = _call_openai_chat(model_name, prompt, temperature, llm_max_tokens) | |
| else: | |
| summary = _call_openai_completion(model_name, prompt, temperature, llm_max_tokens) | |
| map_summaries.append(f"--- Page {idx} Summary ---\n{summary}\n") | |
| # COMBINE step: combine map summaries and produce final summary | |
| combined_text = "\n\n".join(map_summaries) | |
| combine_prompt = combine_template.replace("{text}", combined_text) | |
| # For combine, you might allow more tokens (we reuse llm_max_tokens here) | |
| if model_type == "chat": | |
| final = _call_openai_chat(model_name, combine_prompt, temperature, llm_max_tokens) | |
| else: | |
| final = _call_openai_completion(model_name, combine_prompt, temperature, llm_max_tokens) | |
| return final | |
| except openai.error.OpenAIError as e: | |
| raise gr.Error(f"OpenAI API error: {str(e)}") | |
| except Exception as e: | |
| raise gr.Error(f"Unexpected error: {str(e)}") | |
| def main(): | |
| with gr.Blocks() as demo: | |
| gr.HTML(title) | |
| with gr.Tab("Main"): | |
| with gr.Column(): | |
| pdf_doc = gr.File(label="Uploaded PDF:", file_types=['.pdf']) | |
| with gr.Row(): | |
| submit_button = gr.Button(value="Upload!") | |
| pdf_preview = gr.Textbox(label="PDF Preview:", lines=4, interactive=False) | |
| API_KEY = gr.Textbox(label="OpenAI API Key:", lines=1, type="password") | |
| summarize_button = gr.Button(value="Summarize!") | |
| summarized_text = gr.Textbox(label="Summary", lines=10) | |
| with gr.Tab("Config"): | |
| llm_model = gr.Dropdown(choices=list(model_list.keys()), label="LLM model used", value='gpt-3.5-turbo', interactive=True) | |
| with gr.Row(): | |
| temperature = gr.Slider(minimum=0, maximum=0.5, step=0.1, label="temperature", info=config_info['temperature']) | |
| llm_max_tokens = gr.Radio(choices=[128, 256, 512], value=256, interactive=True, label="LLM max tokens", info=config_info['max_tokens']) | |
| gr.HTML(desc_1) | |
| with gr.Row(): | |
| user_map_prompt = gr.Textbox(label="Map PROMPT", lines=10, interactive=True) | |
| user_comb_prompt = gr.Textbox(label="Combine PROMPT", lines=10, interactive=True) | |
| with gr.Accordion("Default Template", open=False): | |
| with gr.Row(): | |
| default_map_prompt = gr.Textbox(label="Map PROMPT", value=MAP_PROMPT, lines=10, interactive=False) | |
| default_comb_prompt = gr.Textbox(label="Combine PROMPT", value=COMBINE_PROMPT, lines=10, interactive=False) | |
| with gr.Accordion("User Custom Prompt Preview", open=False): | |
| prompt_preview_button = gr.Button(value="View Custom Prompt") | |
| with gr.Row(): | |
| custom_map_view = gr.Textbox(label="Map PROMPT", lines=10, interactive=False) | |
| custom_comb_view = gr.Textbox(label="Combine PROMPT", lines=10, interactive=False) | |
| # preview custom templates (wrapping the user's prompt) | |
| def preview_custom(p): | |
| if not p: | |
| return "" | |
| return generate_template(p) | |
| prompt_preview_button.click(preview_custom, inputs=[user_map_prompt], outputs=[custom_map_view]) | |
| prompt_preview_button.click(preview_custom, inputs=[user_comb_prompt], outputs=[custom_comb_view]) | |
| inputs_list = [API_KEY, llm_model, temperature, llm_max_tokens, user_map_prompt, user_comb_prompt] | |
| submit_button.click(parse_pdf, inputs=[pdf_doc], outputs=[pdf_preview]) | |
| summarize_button.click(summarize_pdf, inputs=inputs_list, outputs=[summarized_text]) | |
| demo.queue().launch(share=True) | |
| if __name__ == "__main__": | |
| main() |