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 = '''

Small PDF Summarizer

This App can be used to summarize small PDF (max. 1 MB, 15 pages)
How to Use:
1. Upload a .PDF from your computer and fill OpenAI API key.
2. Click the "Upload PDF" button, if successful a preview of your PDF text will be shown.
3. Click "Summarize!" and the output will be shown on the textbox below.
You can also change some LLM configurations from the 'config' tab.

''' desc_1 = '''

Custom Prompt Template

You can customize input prompt for the map and combine prompt of the map-reduce summarization pipeline using the textbox below.
Prompt which will be fed into LLM uses the format: {textbox input} + {pdf_text} + "SUMMARY:"
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.
More Info on Map-Reduce for Summarization

''' 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()