Skip to content

add data extraction tutorial - #198

Open
marawilhelmi wants to merge 2 commits into
mainfrom
tutorial
Open

add data extraction tutorial#198
marawilhelmi wants to merge 2 commits into
mainfrom
tutorial

Conversation

@marawilhelmi

@marawilhelmi marawilhelmi commented Jun 30, 2025

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Add a three-part hands-on tutorial for scientific data extraction using LLMs, covering data collection and preprocessing, prompt engineering and model integration, and postprocessing with validation and evaluation.

New Features:

  • Introduce tutorial notebook on collecting scientific paper metadata, PDF processing, text cleaning, and chunking for LLM workflows
  • Add tutorial on using LiteLLM for basic and one-shot prompting to extract information from scientific texts
  • Include tutorial on structured output enforcement with Pydantic schemas, evaluation metrics, fuzzy matching, and unit normalization

@sourcery-ai

sourcery-ai Bot commented Jun 30, 2025

Copy link
Copy Markdown

Reviewer's Guide

This PR adds three comprehensive tutorial notebooks that guide users through an end-to-end scientific data extraction workflow with LLMs, implementing scaffolding and examples for preprocessing, model interfacing, and postprocessing/evaluation.

Class diagram for structured extraction schema (Pydantic models)

classDiagram
    class PolymerExtraction {
        +str name
        +Optional~str~ synthesis_method
        +Optional~List~ temperature
        +Optional~bool~ homopolymer
    }
    class PolymerList {
        +List~PolymerExtraction~ polymers
    }
    PolymerList --> PolymerExtraction : contains
Loading

File-Level Changes

Change Details Files
Create initial data preprocessing tutorial notebook
  • Add markdown sections outlining the extraction workflow
  • Include code cells for Crossref API ingestion and instruction TODOs
  • Implement PDF listing and placeholder code for OCR extraction
  • Define text cleaning function with regex scaffolding
  • Set up semantic chunking with langchain splitter
  • Provide summary of preprocessing steps
tutorial/data_extraction_tutorial_1.ipynb
Add LLM interaction and prompting tutorial
  • Define generic call_llm function using LiteLLM
  • Show basic model call on cleaned text
  • Demonstrate one-shot prompting with example inline
  • Include markdown guidance on prompt variations
  • Outline advanced agent and VLM integration links
tutorial/data_extraction_tutorial_2.ipynb
Introduce controlled output and evaluation notebook
  • Define Pydantic schemas for constrained JSON output
  • Show LLM extraction with instructor.patch into schema
  • Implement precision/recall/F1 evaluation example
  • Add fuzzy matching scaffolding using SequenceMatcher
  • Demonstrate unit normalization via pint library
tutorial/data_extraction_tutorial_3.ipynb

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @marawilhelmi - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • The call to the chunking function is missing. (link)

General comments:

  • Remove or implement all TODO placeholders so the tutorial notebooks are runnable and self-contained rather than leaving stub variables in key code cells.
  • Update the notebook metadata to ensure the kernel and language_info versions accurately reflect the Python environment (e.g. use Python 3.x everywhere instead of mixing with 2.7).
  • Consider refactoring repeated file I/O patterns (listing PDFs, reading/writing text files) into shared helper functions to reduce duplication across the notebooks.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Remove or implement all TODO placeholders so the tutorial notebooks are runnable and self-contained rather than leaving stub variables in key code cells.
- Update the notebook metadata to ensure the kernel and language_info versions accurately reflect the Python environment (e.g. use Python 3.x everywhere instead of mixing with 2.7).
- Consider refactoring repeated file I/O patterns (listing PDFs, reading/writing text files) into shared helper functions to reduce duplication across the notebooks.

## Individual Comments

### Comment 1
<location> `tutorial/data_extraction_tutorial_1.ipynb:162` </location>
<code_context>
+    "import os\n",
+    "import fitz  # PyMuPDF\n",
+    "\n",
+    "pdf_folder = ___ # TODO: Specify the folder path where the PDF files are located\n",
+    "\n",
+    "output_folder = ___ # TODO: Specify the output folder path for the text files\n",
+    "\n",
+    "# Make sure the output folder exists\n",
</code_context>

<issue_to_address>
The folder paths for input and output are left as placeholders.

Please provide example paths or clarify the expected directory structure to help users avoid errors and improve usability.
</issue_to_address>

### Comment 2
<location> `tutorial/data_extraction_tutorial_1.ipynb:180` </location>
<code_context>
+    "        # Extract text from all pages\n",
+    "        full_text = \"\"\n",
+    "        for page in doc:\n",
+    "           # TODO Use .get_text() to extract the text from the documents and add it to full_text\n",
+    "        \n",
+    "        # TODO print the extracted text\n",
</code_context>

<issue_to_address>
The extraction of text from PDF pages is not implemented.

Add a call to .get_text() for each page to populate full_text and ensure the output files contain the extracted text.
</issue_to_address>

### Comment 3
<location> `tutorial/data_extraction_tutorial_1.ipynb:234` </location>
<code_context>
+    "        filtered = matches[0].replace(\"Acknowledgements\", \"\")\n",
+    "    else:\n",
+    "        # If pattern not found, keep original text\n",
+    "        # TODO: define the filtered text when no pattern is found \n",
+    "    \n",
+    "    return filtered"
</code_context>

<issue_to_address>
The fallback for when the regex pattern is not found is not defined.

Assigning filtered = text in the else branch will ensure the function always returns a value and avoids undefined variable errors.
</issue_to_address>

### Comment 4
<location> `tutorial/data_extraction_tutorial_1.ipynb:263` </location>
<code_context>
+    "            content = f.read()\n",
+    "        \n",
+    "        # Clean the content\n",
+    "        cleaned =  # TODO: add the correct call of the cleaning function\n",
+    "\n",
+    "        # TODO: Print the cleaned content\n",
</code_context>

<issue_to_address>
The call to the cleaning function is missing.

This will cause a runtime error since 'cleaned' is not defined. Please call the cleaning function with the correct argument.
</issue_to_address>

### Comment 5
<location> `tutorial/data_extraction_tutorial_1.ipynb:304` </location>
<code_context>
+    "    Splits a given text into overlapping chunks using RecursiveCharacterTextSplitter.\n",
+    "    \"\"\"\n",
+    "    text_splitter = RecursiveCharacterTextSplitter(\n",
+    "        chunk_size=___,       # TODO: Define chunk size\n",
+    "        chunk_overlap=___     # TODO: Define overlap size\n",
+    "    )\n",
+    "    \n",
</code_context>

<issue_to_address>
Chunk size and overlap are left as placeholders in the text splitter.

Without these values, the chunking function will not operate. Please set default values (e.g., chunk_size=1000, chunk_overlap=200) to ensure functionality.
</issue_to_address>

### Comment 6
<location> `tutorial/data_extraction_tutorial_1.ipynb:329` </location>
<code_context>
+    "        with open(os.path.join(cleaned_folder, filename), \"r\", encoding=\"utf-8\") as f:\n",
+    "            content = f.read()\n",
+    "\n",
+    "        chunks = # TODO: call the chunking function with the content\n",
+    "\n",
+    "        print(f\"{len(chunks)} chunks created for {filename}.\")"
</code_context>

<issue_to_address>
The call to the chunking function is missing.

This will cause a runtime error since 'chunks' is undefined. Please call the chunking function with the file content.
</issue_to_address>

### Comment 7
<location> `tutorial/data_extraction_tutorial_2.ipynb:97` </location>
<code_context>
+    "user_prompt = f\"Extract all polymer names from the following text:\\n\\n{document_text}\"\n",
+    "\n",
+    "# Call the model\n",
+    "response = call_llm(___, ___) # TODO: add the prompts to call the call_llm function\n",
+    "\n",
+    "print(response)"
</code_context>

<issue_to_address>
The call to call_llm is missing the required prompt arguments.

This will cause a runtime error; please pass both system and user prompts to call_llm.
</issue_to_address>

### Comment 8
<location> `tutorial/data_extraction_tutorial_3.ipynb:219` </location>
<code_context>
+    "from difflib import SequenceMatcher\n",
+    "\n",
+    "# Define a fuzzy matching function using character similarity\n",
+    "def fuzzy_match(a, b, threshold=___):  # TODO: Try out different similarity thresholds\n",
+    "    \"\"\"\n",
+    "    Returns True if strings `a` and `b` are similar above a certain threshold.\n",
</code_context>

<issue_to_address>
The default threshold for fuzzy matching is left as a placeholder.

A missing threshold will raise a runtime error. Please set a default value, such as 0.8, to avoid this issue.
</issue_to_address>

### Comment 9
<location> `tutorial/data_extraction_tutorial_3.ipynb:240` </location>
<code_context>
+    "for gt in ground_truth:\n",
+    "    for pred in predicted:\n",
+    "        if fuzzy_match(gt, pred, threshold=0.8):  # <-- try different thresholds!\n",
+    "            # TODO: Print the match pair\n",
+    "            print(f\"✓ '{___}' ↔ '{___}'\")\n"
+   ],
</code_context>

<issue_to_address>
The print statement for matched pairs is left as a placeholder.

Please update the print statement to display the actual matched ground truth and predicted values for complete output.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +162 to +164
"pdf_folder = ___ # TODO: Specify the folder path where the PDF files are located\n",
"\n",
"output_folder = ___ # TODO: Specify the output folder path for the text files\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: The folder paths for input and output are left as placeholders.

Please provide example paths or clarify the expected directory structure to help users avoid errors and improve usability.

" # Extract text from all pages\n",
" full_text = \"\"\n",
" for page in doc:\n",
" # TODO Use .get_text() to extract the text from the documents and add it to full_text\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The extraction of text from PDF pages is not implemented.

Add a call to .get_text() for each page to populate full_text and ensure the output files contain the extracted text.

" filtered = matches[0].replace(\"Acknowledgements\", \"\")\n",
" else:\n",
" # If pattern not found, keep original text\n",
" # TODO: define the filtered text when no pattern is found \n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The fallback for when the regex pattern is not found is not defined.

Assigning filtered = text in the else branch will ensure the function always returns a value and avoids undefined variable errors.

" content = f.read()\n",
" \n",
" # Clean the content\n",
" cleaned = # TODO: add the correct call of the cleaning function\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The call to the cleaning function is missing.

This will cause a runtime error since 'cleaned' is not defined. Please call the cleaning function with the correct argument.

Comment on lines +304 to +305
" chunk_size=___, # TODO: Define chunk size\n",
" chunk_overlap=___ # TODO: Define overlap size\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Chunk size and overlap are left as placeholders in the text splitter.

Without these values, the chunking function will not operate. Please set default values (e.g., chunk_size=1000, chunk_overlap=200) to ensure functionality.

" with open(os.path.join(cleaned_folder, filename), \"r\", encoding=\"utf-8\") as f:\n",
" content = f.read()\n",
"\n",
" chunks = # TODO: call the chunking function with the content\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The call to the chunking function is missing.

This will cause a runtime error since 'chunks' is undefined. Please call the chunking function with the file content.

"user_prompt = f\"Extract all polymer names from the following text:\\n\\n{document_text}\"\n",
"\n",
"# Call the model\n",
"response = call_llm(___, ___) # TODO: add the prompts to call the call_llm function\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The call to call_llm is missing the required prompt arguments.

This will cause a runtime error; please pass both system and user prompts to call_llm.

"from difflib import SequenceMatcher\n",
"\n",
"# Define a fuzzy matching function using character similarity\n",
"def fuzzy_match(a, b, threshold=___): # TODO: Try out different similarity thresholds\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The default threshold for fuzzy matching is left as a placeholder.

A missing threshold will raise a runtime error. Please set a default value, such as 0.8, to avoid this issue.

"for gt in ground_truth:\n",
" for pred in predicted:\n",
" if fuzzy_match(gt, pred, threshold=0.8): # <-- try different thresholds!\n",
" # TODO: Print the match pair\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: The print statement for matched pairs is left as a placeholder.

Please update the print statement to display the actual matched ground truth and predicted values for complete output.

@@ -0,0 +1,396 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Therefore, you could download a dataset manually or use a tool to help to automate and speed up this process."

"One of these libraries is crossrefapi."


Reply via ReviewNB

@@ -0,0 +1,396 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next step is to download the relevant papers. There are multiple datasets and tools available which can be used for data mining. While downloading papers, please always be aware of copyright.


Reply via ReviewNB

@@ -0,0 +1,396 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a warning that for Nougat, a GPU is at least recommended


Reply via ReviewNB

@@ -0,0 +1,396 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

irrelevant sections (second line)


Reply via ReviewNB

@@ -0,0 +1,396 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you like to explain what tokens are? I think they were not introduced until this point. Or perhaps you can link to a blog explaining them, e.g, https://www.geeksforgeeks.org/nlp/tokenization-in-natural-language-processing-nlp


Reply via ReviewNB

@@ -0,0 +1,210 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above might be specially useful to link to something that explains the differences especially if here you ask them to fill the different prompts


Reply via ReviewNB

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

@@ -0,0 +1,374 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line #14.        model="gpt-4-turbo",

gpt-4o-mini


Reply via ReviewNB

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps I would also emphasize where the pydantic model comes in and what the retries do

@@ -0,0 +1,374 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the + 1e-8 ?


Reply via ReviewNB

@@ -0,0 +1,374 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to provide a link to the pint docs in case someone wants to use it even if it is not for data extraction?


Reply via ReviewNB

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

@@ -0,0 +1,374 @@
{

@MrtinoRG MrtinoRG Jun 30, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this output correspond to the actual version?


Reply via ReviewNB

@MrtinoRG

Copy link
Copy Markdown
Contributor

So my biggest concern is about the time. You said that you have 3 hours for this which I think might go long. I do not know how do you want to structure it but perhaps propose some exercises, in case someone finishes going through the notebooks, at least it is occupied.

Another minor comment is that there is one test failing 😓

@@ -0,0 +1,396 @@
{

@kjappelbaum kjappelbaum Jul 1, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps also add a note how this tutorial works. That there are parts of codes that still need to be filled in and that those are indicated with _


Reply via ReviewNB

@@ -0,0 +1,396 @@
{

@kjappelbaum kjappelbaum Jul 1, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Therefore -> To do so


Reply via ReviewNB

@@ -0,0 +1,396 @@
{

@kjappelbaum kjappelbaum Jul 1, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you allowed to share the example papers?


Reply via ReviewNB

@@ -0,0 +1,396 @@
{

@kjappelbaum kjappelbaum Jul 1, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps add a note why this chunking method


Reply via ReviewNB

@@ -0,0 +1,374 @@
{

@kjappelbaum kjappelbaum Jul 1, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know if this makes the purpose of matching really clear.

Matching is required because you might have k entries in the ground truth and the model will extract n. Out of those n some might be hallucinated, wrong, the model might also have missed some. Hence, it is non-trivial to construct a mapping of what of the k entries to compare to with the n entries.


Reply via ReviewNB

@kjappelbaum

Copy link
Copy Markdown
Contributor

@marawilhelmi you did not want to merge this, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants