My python program with gradio that suppose can let the LLM generate excel file with the process as follow:

LLM will generate the csv format text as user required
The program will detect the LLM generated output, then convert to excel format
Have a convert to excel button that can let the user to choose to convert the llm output to excel file if he want
Also have multiturn conversation and chat with pdf.
Read and study my code:
"
import os
import time
import pandas as pd
import re
from io import StringIO
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer, BitsAndBytesConfig
import gradio as gr
from threading import Thread
from PyPDF2 import PdfReader # Import PyPDF2 for PDF text extraction
MODEL_PATH = r"C:\Users\shermanhonsf\Desktop\Python\llama_develop\model\Llama3.2-1B-Instruct-HF-Model" # Your model path

TITLE = """
Chat with Llama 3.2 1B
"""

PLACEHOLDER = """
Hi, I'm Llama. Ask me anything or upload a PDF to chat about its content.
"""

CSS = """
.duplicate-button {
margin: auto !important;
color: white !important;
background: black !important;
border-radius: 100vh !important;
}
h3 {
text-align: center;
}
"""

Set the device to "cuda" if you have a compatible GPU, otherwise "cpu"
device = "cuda" if torch.cuda.is_available() else "cpu"

Configure quantization if needed (optional)
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)

Load the tokenizer and model from the local directory
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
model = AutoModelForCausalLM.from_pretrained(
MODEL_PATH,
torch_dtype=torch.bfloat16,
device_map="auto",
# quantization_config=quantization_config
)

Move the model to the appropriate device
model.to(device)

def extract_pdf_text(pdf_path):
"""Extract all text from a PDF file."""
try:
reader = PdfReader(pdf_path)
text = ""
for page in reader.pages:
text += page.extract_text()
return text
except Exception as e:
return f"Error reading PDF: {e}"

Function to extract CSV data from LLM output
def extract_csv_data(llm_output):
# Define start and end markers for the CSV content
start_marker = "#START_CSV"
end_marker = "#END_CSV"


# Locate the markers in the LLM output
start_index = llm_output.find(start_marker)
end_index = llm_output.find(end_marker)

if start_index == -1 or end_index == -1:
    print("CSV markers not found in the output.")
    return None

# Extract the CSV content between the markers
csv_content = llm_output[start_index + len(start_marker):end_index].strip()
return csv_content
Function to convert the extracted CSV data to an Excel file
def convert_to_excel(llm_output, output_file='output.xlsx'):
# Extract the CSV data from the LLM output
csv_data = extract_csv_data(llm_output)


# If no CSV data is found, return or handle the error
if not csv_data:
    print("No CSV data found.")
    return "No CSV data to convert."

# Convert the CSV data into a Pandas DataFrame
try:
    df = pd.read_csv(StringIO(csv_data), sep=",")
    # Save the DataFrame to an Excel file
    df.to_excel(output_file, index=False)
    return f"Excel file saved as: {output_file}"
except Exception as e:
    print(f"Error converting to Excel: {e}")
    return f"Error converting to Excel: {e}"
Function to handle the "Convert to Excel" button click
def handle_convert_to_excel(llm_output):
if not llm_output.strip():
return "No output to convert. Please generate something first."


# Call the conversion function and return the result
return convert_to_excel(llm_output, output_file='llm_output.xlsx')
def stream_chat(message: str, history: list, system_prompt: str ="You are a helpful assistant. When asked to generate tabular data, please follow these rules:\n"
"1. Produce the specified data as a CSV table with a header row.\n"
"2. Ensure that fields in the CSV are separated by commas (',').\n"
"3. Enclose the CSV output with markers: \n"
" - Begin with #START_CSV.\n"
" - End with #END_CSV.\n",
temperature: float = 0.8, max_new_tokens: int = 1024, top_p: float = 1.0, top_k: int = 20, penalty: float = 1.2, pdf_text: str = ""):
print(f'message: {message}')
print(f'history: {history}')
print(f'system_prompt: {system_prompt}')
print(f'temperature: {temperature}')
print(f'max_new_tokens: {max_new_tokens}')
print(f'top_p: {top_p}')
print(f'top_k: {top_k}')
print(f'penalty: {penalty}')
print(f'Message: {message}')
print(f'History: {history}')
print(f'PDF Text: {pdf_text}')


conversation = [
    {"role": "system", "content": system_prompt}
]

# Add PDF content to the system prompt if provided
if pdf_text.strip():
    pdf_summary = f"The user uploaded a PDF with the following content:\n{pdf_text[:2000]}...\n(This is a truncated summary if the text is too long.)"
    conversation.append({"role": "system", "content": pdf_summary})

for prompt, answer in history:
    conversation.extend([
        {"role": "user", "content": prompt},
        {"role": "assistant", "content": answer},
    ])

conversation.append({"role": "user", "content": message})

# Prepare input IDs
input_ids = tokenizer.apply_chat_template(
    conversation,
    # tokenize=True,
    add_generation_prompt=True,
    return_tensors="pt"
).to(device)

streamer = TextIteratorStreamer(
    tokenizer,
    timeout=60.0,
    skip_prompt=True,
    skip_special_tokens=True
)

generate_kwargs = dict(
    input_ids=input_ids,
    max_new_tokens=max_new_tokens,
    do_sample=False if temperature == 0 else True,
    top_p=top_p,
    top_k=top_k,
    temperature=temperature,
    eos_token_id=[128001, 128008, 128009],
    streamer=streamer,
)

# Generate the response in a background thread
with torch.no_grad():
    thread = Thread(target=model.generate, kwargs=generate_kwargs)
    thread.start()

# Stream the response back to the user
buffer = ""
for new_text in streamer:
    buffer += new_text
    yield buffer
Create the Gradio interface
chatbot = gr.Chatbot(height=600, show_copy_button=True, layout="panel", placeholder=PLACEHOLDER)

with gr.Blocks(css=CSS) as demo:
gr.HTML(TITLE)


# Add file uploader for PDF input
pdf_file = gr.File(label="Upload PDF", type="filepath")
pdf_textbox = gr.Textbox(label="Extracted PDF Text", placeholder="PDF content will appear here after upload.", lines=10, interactive=False)

def process_pdf(file):
    if file is not None:
        pdf_text = extract_pdf_text(file.name)
        return pdf_text
    return "No PDF uploaded."

pdf_file.change(process_pdf, inputs=pdf_file, outputs=pdf_textbox)

# Chatbot interface
chatbot = gr.Chatbot(height=600, show_copy_button=True, layout="panel", placeholder=PLACEHOLDER)
llm_output_box = gr.Textbox(label="LLM Output", placeholder="Generated output will appear here.", lines=10, interactive=False)

# Add the "Convert to Excel" button
convert_button = gr.Button("Convert to Excel")
excel_status_box = gr.Textbox(label="Excel Conversion Status", placeholder="Excel conversion status will appear here.", interactive=False)

# Connect the button to the conversion function
convert_button.click(handle_convert_to_excel, inputs=llm_output_box, outputs=excel_status_box)

# Chat interface
gr.ChatInterface(
    fn=stream_chat,
    chatbot=chatbot,
    fill_height=True,
    additional_inputs_accordion=gr.Accordion(
        label="⚙️ Parameters",
        open=False,
        render=False
    ),
    additional_inputs=[
        gr.Textbox(
            value="You are a helpful assistant. When asked to generate tabular data, please follow these rules:\n"
                "1. Produce the specified data as a CSV table with a header row.\n"
                "2. Ensure that fields in the CSV are separated by commas (',').\n"
                "3. Enclose the CSV output with markers: \n"
                "   - Begin with `#START_CSV`.\n"
                "   - End with `#END_CSV`.\n",
            label="System Prompt",
            render=False,
        ),
        gr.Slider(
            minimum=0,
            maximum=1,
            step=0.1,
            value=0.8,
            label="Temperature",
            render=False,
        ),
        gr.Slider(
            minimum=128,
            maximum=8192,
            step=1,
            value=1024,
            label="Max new tokens",
            render=False,
        ),
        gr.Slider(
            minimum=0.0,
            maximum=1.0,
            step=0.1,
            value=1.0,
            label="Top_p",
            render=False,
        ),
        gr.Slider(
            minimum=1,
            maximum=20,
            step=1,
            value=20,
            label="Top_k",
            render=False,
        ),
        gr.Slider(
            minimum=0.0,
            maximum=2.0,
            step=0.1,
            value=1.2,
            label="Repetition penalty",
            render=False,
        ),
        pdf_textbox  # Pass the extracted PDF text as an additional input
    ],
    examples=[
        ["What are the main themes explored in the movie 'Dune', and how do they reflect contemporary issues?"],
        ["What are 3 practical techniques I can use to prioritize tasks more effectively in a busy work schedule?"],
        ["How does blockchain technology work, and what are its potential applications beyond cryptocurrencies?"],
        ["Show me a code snippet of a website's sticky header in CSS and JavaScript."],
    ],
    cache_examples=False,
)

# Update the LLM output box when the chatbot generates a response
def update_llm_output(message, history):
    if history:
        print("update_llm_output function test: Have history")
        return history[-1][1]  # Return the assistant's latest response
    print("update_llm_output function test: No history")
    return ""

chatbot.change(update_llm_output, inputs=None, outputs=llm_output_box)
if name == "main":
demo.launch(debug=True)

"
However, in update_llm_output function, the history is not found, I always get update_llm_output function test: No history. So, excel_status_box show nothing. I think it's because the history is in the stream_chat() function, but update_llm_output cannot use the history in other function directly, please fix it. I think you can use the class method. Show the added or modified code in the correct order relative to the original code.