Scientific Papers Summarized for You

Scientific Papers Summarized for You

  • Giacomo Vaccario
  • July 13, 2026
Table of Contents

Reading scientific literature is tough. Research papers are dense, heavily technical, and often excruciatingly long. To make matters worse, different readers need completely different takeaways from the exact same paper—a developer needs technical details, while a decision-maker just needs the high-level impact.

The solution? SummaryAgent, an automated framework built using AI agents designed to break down dense research papers into tailormade, digestible insights.

This project was built during “AI for Science Communication” course at ETH Zurich (January 2025) led by Mirko Bischofberger, focusing on using AI to bridge the gap between complex research and accessible information.

The Experimentation Phase

Building a reliable summarizer isn’t as simple as asking a chatbot to “summarize this paper.” Finding the sweet spot required trial and error across models and techniques:

  • Models & LLMs: Tested options including ChatGPT, LLaMA 2, and Mistral.
  • Prompting Strategies: Experimented with Zero-Shot, Few-Shot, and Chain-of-Thought prompting to force models to reason through complex methodologies before rendering a final summary.
  • Tone & Persona Crafting: Fine-tuned prompts to instruct agents to adopt specific language styles and output formats depending on the target audience.

Disclaimer: I decided to make explicit that the summaries were generated by an AI agent, and to make people aware that the summaries are not perfect and may contain errors. Please refer to the original paper for critical research tasks.

Final System Persona

First, we define the persona

You are an expert science communicator. You will receive the full text of a scientific paper.

Your task:
Generate structured output in valid JSON only (no markdown, no explanations).

General rules:

* Focus on results and implications, not background
* Do NOT use em dashes
* Add exactly one spelling mistake per output 
* Do NOT use a serial comma

STRICT rules:
* Describe results impersonally (e.g., “The analysis shows…”, “Results indicate…”).
* DO NOT mention authors or use pronouns like “they”
* DO NOT use human actors as grammatical subjects (e.g., authors, researchers, scientists, “they”).
* DO NOT personify the paper or attribute actions to people or groups.

Second, we define the output format

Output JSON schema:

{
"audience_1": {
"summary": "...",
"quote": "..."
},
"audience_2": {
"part_1": "...",
"part_2": "...",
"quote": "..."
},
"audience_3": {
"summary": "...",
"quote": "..."
}
}

Third, we define the requirements for each audience

Requirements:

Audience 1:
* General audience
* ~120 words
* Explain motivation, topic, key results
* Kincaid-Flesh Reading Ease of 60
* Do not add a title to the summary

Audience 2:
Part 1:

* Why it matters for scientists
* ≤100 words
* Career/research implications
* Direct tone ("you may want to...")

Part 2:

* Background, methods, novelty
* ≤100 words

Audience 3:

* Policy/stakeholders
* ~100-120 words
* Focus on implications and applications

Quotes:

* One per audience
* Define the key message (1 sentence)
* Select 1 quote (1-2 sentences, exact text) that directly supports it

Quote must:
* Contain a specific result or conclusion
* Not be background or vague
* Be understandable on its own
* 1-2 sentences
* Must be exact text from the paper

Return ONLY valid JSON.

Note that we added a request to generate valid JSON only, to avoid the model generating any text outside of the JSON structure. This is important for downstream processing and automation.

Automation: Moving Beyond Manual Uploads

Everyone is good at uploading a PDF on a chatbot and asking for a summary. The hard part is automating everything—having a system that can read the PDF, extract the text, summarize it, push it to a database, and display the summary nicely on a front-end interface.

To keep full control over what gets summarized (and keep costs predictable), I opted for a local Python script that checks my current lists of papers and compares it with the list of papers that have already been summarized. If a paper is new, it will be summarized; if it has already been processed, it will be skipped. This file is executed locally and manages the ingestion workflow. The summaries are then pushed to a GitHub repository, which is automatically deployed to the website.

I will past here excerpts of the code that I used to build this system, which is available on GitHub:

I use the HuggingFace API to access LLMs, which allows me to use different models and switch between them easily.


API_URL = "https://router.huggingface.co/v1/chat/completions"
    
HEADERS = {
        "Authorization": f"Bearer {HF_TOKEN}",
        "Content-Type": "application/json",
    }

PDF_FOLDER = "content/english/publications"

def extract_text(pdf_path):
    reader = PdfReader(pdf_path)
    text = ""
    for page in reader.pages:
        text += page.extract_text() or ""
    return text[:8000]  # keep it safe for API limits

def summarize(text, model="meta-llama/Llama-3.1-8B-Instruct", max_tokens=7000):
    def build_payload(model, text):
        # Detect Mistral vs chat-based models
        is_mistral = "mistral" in model.lower()
        if is_mistral:
            prompt = f"[INST] {BOT_ROLE}\n\n{text} [/INST]"
            return {
                "model": model,
                "inputs": prompt,
                "parameters": {
                    "temperature": 0.7,
                    "max_new_tokens": max_tokens
                }
            }
        else:
            return {
                "model": model,
                "messages": [
                    {"role": "system", "content": BOT_ROLE},
                    {"role": "user", "content": text}
                ],
                "temperature": 0.7,
                "max_tokens": max_tokens,
                "response_format": {"type": "json_object"}
            }

    payload = build_payload(model, text)
    response = requests.post(API_URL, headers=HEADERS, json=payload)

    print("Status:", response.status_code)
    #print(response.text)

    response.raise_for_status()
    data = response.json()

    # Handle different response formats
    if "choices" in data:
        choice = data["choices"][0]
        return (
            choice.get("message", {}).get("content") or
            choice.get("text")
        )
    elif "generated_text" in data:
        return data["generated_text"]
    else:
        return str(data)

The script also handles the output formatting, saving the summaries in a structured way that can be easily displayed on a website.

def json_to_html(data):
    return f"""
<p>{data['audience_1']['summary']}</p>

<pre>
{data['audience_1']['quote']}
</pre>

<details class="custom-details">
  <summary><strong>Why This Matters for Scientists</strong></summary>
  <p>{data['audience_2']['part_1']}</p>
</details>

<details class="custom-details">
  <summary><strong>Quick Technical Overview</strong></summary>
  <p>{data['audience_2']['part_2']}</p>

  <pre>
{data['audience_2']['quote']}
  </pre>
</details>

<details class="custom-details">
  <summary><strong>Summary for Policy Makers</strong></summary>
  <p>{data['audience_3']['summary']}</p>

  <pre>
{data['audience_3']['quote']}
  </pre>
</details>

<details class="custom-details" data-sentinel="ai-disclaimer-v1">
  <summary><strong>Disclaimer</strong></summary>
  <p>The above summaries were generated with the assistance of an AI system.</p>
</details>
"""

Disclaimer: I decided to make explicit that the summaries were generated by an AI agent, and to make people aware that the summaries are not perfect and may contain errors.

def main():
    for root, dirs, files in os.walk(PDF_FOLDER):
        for file in files:
            if file.lower().endswith(".pdf"):
                # check if index.md already exists
                if os.path.exists(os.path.join(root, "index.md")):
                    # check if it contains the ai-disclaimer
                    summary_is_done = has_sentinel(os.path.join(root, "index.md"))
                    if summary_is_done:
                        print(f"Summary already exists for {file}, skipping...")
                        continue
                path = os.path.join(root, file)

                print(f"Processing {file}...")
                text = extract_text(path)
                summary = summarize(text)
                title = file.replace(".pdf", "")
                save_markdown(summary, root, filename="index.md") 

    # os.system("git add .")
    # os.system('git commit -m "auto content update"')
    # os.system("git push")
if __name__ == "__main__":
    main()

Note: The processing script automatically handles git push for you, pushing updated summaries and database logs seamlessly once processing is complete. I have commented out the git commands in the code snippet above to avoid accidental pushes during testing.

Related Posts

Mainstreaming Wood in Construction: A Holistic Approach

Mainstreaming Wood in Construction: A Holistic Approach

The building sector is one of the highest CO2 emitters, and thanks to modern technologies, using more sustainable construction materials can help deliver net-zero emissions as well as good housing. Using timber would not only increase carbon storage in our buildings, but would also avoid the CO2 emissions of conventional building materials such as concrete - which is still the default.

Read More