juniorjukeko's picture
Update app.py
62dfe15 verified
Raw History Blame Contribute Delete
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()