Skip to content

Improve evaluation output by add more info : MaRefactor metrics computation with structured outputso & Micro scores & S… - #17

Open
arthrod wants to merge 1 commit into
cicero-im:mainfrom
Xiaomin-HUANG:feature/evaluation
Open

Improve evaluation output by add more info : MaRefactor metrics computation with structured outputso & Micro scores & S…#17
arthrod wants to merge 1 commit into
cicero-im:mainfrom
Xiaomin-HUANG:feature/evaluation

Conversation

@arthrod

@arthrod arthrod commented Feb 18, 2026

Copy link
Copy Markdown
Collaborator

…cores per label

Summary by Sourcery

Add richer evaluation metrics output including macro, micro, and per-label scores, and expose a tabular view helper for inspection.

New Features:

  • Return a structured metrics dictionary with micro, macro, and per-class precision/recall/F1 scores from evaluation instead of a single string and F1 value.
  • Introduce a helper method to render evaluation metrics as a sorted pandas DataFrame, including micro and macro averages and per-class scores.

Bug Fixes:

  • Fix division-by-zero warning handling to correctly trigger when any denominator element is zero.

Enhancements:

  • Round precision, recall, and F1 computations to two decimal places for more readable evaluation outputs.

@sourcery-ai

sourcery-ai Bot commented Feb 18, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors evaluation metrics computation to return detailed micro, macro, and per-label scores, adjusts division/warning logic, and adds a helper to pretty-print evaluation results as a DataFrame.

Sequence diagram for updated evaluation and metrics reporting flow

sequenceDiagram
    actor User
    participant Model
    participant Evaluator
    participant Metrics as compute_prf
    participant Calc as _calculate_metrics

    User->>Model: evaluate(dataset)
    Model->>Model: run_inference_on_batches
    Model->>Evaluator: __init__(all_trues, all_preds)
    Model->>Evaluator: evaluate()
    Evaluator->>Evaluator: transform_data()
    Evaluator->>Metrics: compute_prf(all_true_typed, all_outs_typed)
    Metrics->>Metrics: flatten_for_eval(y_true, y_pred)
    Metrics->>Metrics: extract_tp_actual_correct
    Metrics->>Calc: _calculate_metrics(tp_sum, pred_sum, true_sum, macro)
    Calc-->>Metrics: per_class_array, macro_metrics
    Metrics->>Calc: _calculate_metrics(tp_sum_micro, pred_sum_micro, true_sum_micro, micro)
    Calc-->>Metrics: micro_metrics
    Metrics-->>Evaluator: all_metrics
    Evaluator-->>Model: all_metrics
    Model-->>User: all_metrics

    User->>Model: beautiful_df_print(all_metrics)
    Model->>Model: build_micro_macro_dataframe
    Model->>Model: build_per_class_dataframe
    Model-->>User: dataframe
Loading

Class diagram for updated evaluation and metrics utilities

classDiagram
    class Evaluator {
        - all_true
        - all_pred
        + transform_data()
        + evaluate()
    }

    class Model {
        + evaluate(dataset)
        + beautiful_df_print(eval_results)
        + encode_labels(labels, batch_size)
    }

    class MetricsFunctions {
        + flatten_for_eval(y_true, y_pred)
        + compute_prf(y_true, y_pred)
        + _calculate_metrics(tp, pred, true, average)
        + _prf_divide(numerator, denominator, metric, modifier, average, warn_for, zero_division)
    }

    Model --> Evaluator : uses
    Evaluator --> MetricsFunctions : uses
    Model --> MetricsFunctions : uses in beautiful_df_print
Loading

File-Level Changes

Change Details Files
Make PRF division more robust to array denominators and round metric outputs to two decimals.
  • Change zero-division warning condition to use np.any on array denominators instead of scalar comparison.
  • Round the result of _prf_divide to two decimal places before returning.
gliner/evaluation/evaluator.py
Refactor compute_prf to compute and return micro, macro, and per-class metrics instead of a single micro aggregate.
  • Change compute_prf signature to drop the average argument and return a dictionary of metrics.
  • Extract metric computation into a new _calculate_metrics helper that handles micro and macro averaging and f-score calculation.
  • Compute per-class precision/recall/f-score arrays and aggregate them into macro averages.
  • Adjust metric calculations to use the new helper for both micro and macro cases.
gliner/evaluation/evaluator.py
Update Evaluator.evaluate to return the full metrics structure instead of a formatted string and single F1 score.
  • Change evaluate to call compute_prf and return the resulting metrics dictionary directly.
  • Add a docstring documenting the new nested metrics output format.
gliner/evaluation/evaluator.py
Update model evaluation API to return the full metrics dictionary and add a pretty-print helper for tabular display.
  • Change model.evaluate to return the full evaluation results dictionary from Evaluator.evaluate instead of a formatted string and F1 score.
  • Introduce beautiful_df_print method to build a pandas DataFrame combining micro, macro, and per-class metrics and sort per-class rows by f_score.
  • Insert a separator row between aggregate and per-class metrics in the DataFrame.
gliner/model.py

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

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @arthrod, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the evaluation capabilities by introducing a more granular breakdown of performance metrics. Instead of a single F1-score, users will now receive micro-averaged, macro-averaged, and individual scores for each label, presented in a clear, tabular format. This enhancement aims to provide a richer understanding of model strengths and weaknesses across different classes.

Highlights

  • Enhanced Evaluation Metrics: The evaluation system now provides more comprehensive metrics, including micro-averaged, macro-averaged, and per-class precision, recall, and F1-scores, offering a deeper insight into model performance.
  • Refactored Metric Calculation: The core logic for calculating precision, recall, and F1-scores has been modularized into a new helper function, _calculate_metrics, improving code organization and reusability.
  • Improved Output Formatting: A new beautiful_df_print method has been added to gliner.model.py to present the detailed evaluation results in a well-structured pandas DataFrame, making the output more readable and easier to analyze.
Changelog
  • gliner/evaluation/evaluator.py
    • Imported the pandas library for data manipulation.
    • Modified _prf_divide to use np.any for checking zero denominators and to round results to two decimal places.
    • Refactored compute_prf to orchestrate metric calculation and return a dictionary containing micro, macro, and per-class scores.
    • Introduced _calculate_metrics to encapsulate the logic for computing precision, recall, and F1-scores based on the averaging strategy.
    • Updated Evaluator.evaluate to return the comprehensive dictionary of evaluation results.
  • gliner/model.py
    • Imported the pandas library.
    • Modified the evaluate method to return the full dictionary of evaluation results from the Evaluator.
    • Added a new method beautiful_df_print to format the detailed evaluation results into a pandas DataFrame for improved readability.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai

coderabbitai Bot commented Feb 18, 2026

Copy link
Copy Markdown
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Evaluation metrics now provide per-class, macro, and micro breakdowns for comprehensive analysis.
    • Added formatted output for displaying evaluation results in a more readable structure.
  • Bug Fixes

    • Improved handling of zero-division errors in metric calculations.
  • Changes

    • Metrics are now rounded to 2 decimal places for consistency.

Walkthrough

The changes refactor evaluation metrics computation to produce structured outputs with per-class, micro, and macro metrics, while improving numeric stability in division operations. The model's evaluate method now returns the full results dictionary instead of a tuple, and a new method enables formatted display of these results in a pandas DataFrame.

Changes

Cohort / File(s) Summary
Evaluation Metrics Computation
gliner/evaluation/evaluator.py
Enhanced _prf_divide with safer zero-division checking and result rounding. Signature of compute_prf changed to accept typed lists and removed average parameter; now returns comprehensive metrics dict with "macro", "micro", and "per_class" keys. _calculate_metrics extended to support both "micro" and "macro" averaging with improved precision/recall/f-score handling and rounding.
Model Integration & Display
gliner/model.py
Updated evaluate method to return full results dictionary from evaluator instead of (out, f1) tuple. Added new public method beautiful_df_print to format evaluation results as a pandas DataFrame with micro/macro metrics and per-class details.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Metrics now bloom in measured array,
Per-class whispers guide the way,
Macro, micro, all aligned,
Division safer, results refined—
A DataFrame frames our findings bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly outlines the changeset: structured metrics output with micro/macro/per-class scores, a helper method for tabular display, division-by-zero fix, and rounding enhancements. It aligns well with the actual code changes in both evaluator.py and model.py.
Title Check ✅ Passed Title check skipped as CodeRabbit has written the PR title.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@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 - I've found 2 issues, and left some high level feedback:

  • Consider avoiding rounding inside _prf_divide and _calculate_metrics (and instead rounding only at presentation time) so that low-level helpers remain numerically precise and reusable for other computations.
  • The evaluation logic is now split between compute_prf and _calculate_metrics with mixed return types (tuple for macro, dict for micro); aligning these to a single, consistent return shape would make the API easier to reason about and less error-prone.
  • The beautiful_df_print method is presentation/UI-focused and tightly coupled to pandas; you might want to move it out of the core Model class into a separate utility to keep the model implementation focused on modeling and evaluation logic.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider avoiding rounding inside `_prf_divide` and `_calculate_metrics` (and instead rounding only at presentation time) so that low-level helpers remain numerically precise and reusable for other computations.
- The evaluation logic is now split between `compute_prf` and `_calculate_metrics` with mixed return types (tuple for macro, dict for micro); aligning these to a single, consistent return shape would make the API easier to reason about and less error-prone.
- The `beautiful_df_print` method is presentation/UI-focused and tightly coupled to pandas; you might want to move it out of the core `Model` class into a separate utility to keep the model implementation focused on modeling and evaluation logic.

## Individual Comments

### Comment 1
<location> `gliner/evaluation/evaluator.py:27-30` </location>
<code_context>
         result[denominator == 0] = 0.0 if zero_division in ["warn", 0] else 1.0

-    if denominator == 0 and zero_division == "warn" and metric in warn_for:
+    if np.any(denominator == 0) and zero_division == "warn" and metric in warn_for:
         msg_start = f"{metric.title()}"
         if "f-score" in warn_for:
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Rounding in `_prf_divide` reduces numeric precision and pushes formatting concerns into a low‑level helper.

Previously this helper returned raw float/array values and left rounding to the presentation layer. Rounding to 2 decimals here means all downstream uses (including F-score, which already rounds) now operate on truncated values, increasing the risk of compounded rounding errors (e.g., micro scores derived from already-rounded per-class metrics) and reducing reusability for callers that need full precision. It would be safer to return the unrounded `result` and apply rounding only in the final user-facing layer (e.g., `_calculate_metrics` or printing).

Suggested implementation:

```python
    return result

```

To fully follow your suggestion, you will also need to:
1. Identify the user-facing layer(s) that consume `_prf_divide` (e.g., `_calculate_metrics`, reporting/printing functions).
2. Apply rounding there, just before presenting values to the user (e.g., `np.round(metric_values, 2)` or appropriate formatting), ensuring that internal computations (micro/macro averaging, aggregations) continue to use full-precision results from `_prf_divide`.
</issue_to_address>

### Comment 2
<location> `gliner/evaluation/evaluator.py:117-126` </location>
<code_context>
+
+    return all_metrics
+
+def _calculate_metrics(tp,
+                    pred,
+                    true,
</code_context>

<issue_to_address>
**question:** Macro averages are simple means over all classes, which may be skewed by classes with zero support.

In the `average == "macro"` branch, precision/recall/F1 are averaged as `precision.sum() / len(precision)`, which gives equal weight to classes even when they have zero support (no true or predicted instances, depending on `_prf_divide`). That means unseen labels still affect the macro score. Please confirm this matches your intended definition of macro metrics; if not, consider excluding zero-support classes from the average or switching to a support-weighted scheme.
</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 +27 to 30
if np.any(denominator == 0) and zero_division == "warn" and metric in warn_for:
msg_start = f"{metric.title()}"
if "f-score" in warn_for:
msg_start += " and F-score" if metric in warn_for else "F-score"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): Rounding in _prf_divide reduces numeric precision and pushes formatting concerns into a low‑level helper.

Previously this helper returned raw float/array values and left rounding to the presentation layer. Rounding to 2 decimals here means all downstream uses (including F-score, which already rounds) now operate on truncated values, increasing the risk of compounded rounding errors (e.g., micro scores derived from already-rounded per-class metrics) and reducing reusability for callers that need full precision. It would be safer to return the unrounded result and apply rounding only in the final user-facing layer (e.g., _calculate_metrics or printing).

Suggested implementation:

    return result

To fully follow your suggestion, you will also need to:

  1. Identify the user-facing layer(s) that consume _prf_divide (e.g., _calculate_metrics, reporting/printing functions).
  2. Apply rounding there, just before presenting values to the user (e.g., np.round(metric_values, 2) or appropriate formatting), ensuring that internal computations (micro/macro averaging, aggregations) continue to use full-precision results from _prf_divide.

Comment on lines +117 to 126
def _calculate_metrics(tp,
pred,
true,
average : Literal["micro","macro"],
):
precision = _prf_divide(
numerator=tp_sum,
denominator=pred_sum,
numerator=tp,
denominator=pred,
metric="precision",
modifier="predicted",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

question: Macro averages are simple means over all classes, which may be skewed by classes with zero support.

In the average == "macro" branch, precision/recall/F1 are averaged as precision.sum() / len(precision), which gives equal weight to classes even when they have zero support (no true or predicted instances, depending on _prf_divide). That means unseen labels still affect the macro score. Please confirm this matches your intended definition of macro metrics; if not, consider excluding zero-support classes from the average or switching to a support-weighted scheme.

@coderabbitai coderabbitai Bot changed the title Improve evaluation output by add more info : Macro & Micro scores & S… Improve evaluation output by add more info : MaRefactor metrics computation with structured outputso & Micro scores & S… Feb 18, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request significantly enhances the evaluation output by providing macro, micro, and per-label scores, which is a great improvement for model analysis. The introduction of a helper method to display these metrics in a DataFrame is also a nice addition for usability. The fix for the division-by-zero warning is a crucial correction.

I've identified a critical issue that could lead to a crash when no entities are present in the evaluation data. I've also included a few medium-severity suggestions to improve code quality and documentation accuracy. Overall, these are valuable changes.

Comment on lines +152 to +154
macro_precision = np.round(precision.sum()/len(precision),decimals=2)
macro_recall = np.round(recall.sum()/len(recall),decimals=2)
macro_f_score = np.round(f_score.sum()/ len(f_score), decimals=2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

These calculations can lead to a ZeroDivisionError if precision, recall, or f_score are empty arrays. This would happen if there are no predictable entity types in the evaluation data, causing the program to crash. Using np.mean with np.nan_to_num provides a more robust way to calculate the macro average, gracefully handling the empty case by resulting in 0.0.

Suggested change
macro_precision = np.round(precision.sum()/len(precision),decimals=2)
macro_recall = np.round(recall.sum()/len(recall),decimals=2)
macro_f_score = np.round(f_score.sum()/ len(f_score), decimals=2)
macro_precision = np.round(np.nan_to_num(np.mean(precision)), decimals=2)
macro_recall = np.round(np.nan_to_num(np.mean(recall)), decimals=2)
macro_f_score = np.round(np.nan_to_num(np.mean(f_score)), decimals=2)

from collections import defaultdict
from typing import Union, List, Literal

import pandas as pd

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The pandas library is imported here but it is not used within this file. It appears to be used in gliner/model.py for the beautiful_df_print function, where it is also correctly imported. To maintain clean code and avoid unnecessary dependencies in this module, this import should be removed.

recall = _prf_divide(
numerator=tp_sum,
denominator=true_sum,
numerator=tp, # TODO check

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

There is a TODO check comment remaining in the code. The logic here, using tp as the numerator for recall, is correct. This leftover comment should be removed to clean up the code.

Suggested change
numerator=tp, # TODO check
numerator=tp,

Comment on lines +188 to +197
"""
output : {

"per_class":{"tag1":{"precision":int, "recall":int,"f_score":int},
"tag2":{}...
},
"micro":{"precision":int, "recall":int,"f_score":int},
"macro":{"precision":int, "recall":int,"f_score":int},
}
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The docstring incorrectly specifies int as the data type for precision, recall, and f-score. These metrics are floating-point numbers. Updating the type to float will make the documentation accurate and more helpful for developers using this function.

Suggested change
"""
output : {
"per_class":{"tag1":{"precision":int, "recall":int,"f_score":int},
"tag2":{}...
},
"micro":{"precision":int, "recall":int,"f_score":int},
"macro":{"precision":int, "recall":int,"f_score":int},
}
"""
"""
output : {
"per_class":{"tag1":{"precision":float, "recall":float,"f_score":float},
"tag2":{}...
},
"micro":{"precision":float, "recall":float,"f_score":float},
"macro":{"precision":float, "recall":float,"f_score":float},
}
"""

Comment thread gliner/model.py
Comment on lines +664 to +674
"""
eval_results :
{

"per_class":{"tag1":{"precision":int, "recall":int,"f_score":int},
"tag2":{}...
},
"micro":{"precision":int, "recall":int,"f_score":int},
"macro":{"precision":int, "recall":int,"f_score":int},
}
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The docstring indicates that the metric values (precision, recall, f_score) are of type int. However, these are floating-point numbers. The type hint should be float to accurately describe the data structure.

Suggested change
"""
eval_results :
{
"per_class":{"tag1":{"precision":int, "recall":int,"f_score":int},
"tag2":{}...
},
"micro":{"precision":int, "recall":int,"f_score":int},
"macro":{"precision":int, "recall":int,"f_score":int},
}
"""
"""
eval_results :
{
"per_class":{"tag1":{"precision":float, "recall":float,"f_score":float},
"tag2":{}...
},
"micro":{"precision":float, "recall":float,"f_score":float},
"macro":{"precision":float, "recall":float,"f_score":float},
}
"""

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gliner/evaluation/evaluator.py`:
- Around line 150-155: The current macro aggregation divides by len(precision)
which raises ZeroDivisionError when there are no labels; inside the same else
branch guard on an empty metrics array (e.g., if len(precision) == 0 or
precision.size == 0) and set macro_precision, macro_recall, macro_f_score to 0.0
(or np.nan if you prefer) instead of computing the division, then return
per_class_array (which may be empty) and the macro dict; reference the variables
precision, recall, f_score and the returned per_class_array to locate where to
add the guard.
- Around line 188-201: The docstring for the method currently claims integer
metrics but the method returns a dict of rounded floats (the variable
results_all returned from compute_prf after transform_data). Update the
docstring to state that the return value is a dict containing per_class, micro
and macro entries with float precision/recall/f_score (rounded floats), and show
the exact dict structure and example types (e.g. "precision": float) to match
compute_prf/results_all; mention transform_data and compute_prf in the
description so readers can trace the data flow.

In `@gliner/model.py`:
- Around line 656-661: The docstring for the evaluate() method no longer matches
its return type; update the evaluate() docstring (the one for the
Evaluator.evaluate / evaluate() method referenced by evaluator.evaluate and the
caller using all_results) to state that it returns a dict of metrics/results
(not a tuple with F1), describe the dict keys and value types (e.g., overall F1,
per-class metrics, any other entries), and remove any mention of returning a
tuple (out, f1) so callers and readers understand the new return structure.
- Line 5: The module-level "import pandas as pd" causes import errors for users
without pandas; either add "pandas" to project dependencies in
pyproject.toml/requirements.txt, or lazy-import it inside the function
beautiful_df_print in gliner.model: remove the top-level import, add "import
pandas as pd" at the start of beautiful_df_print, catch ImportError and raise a
clear RuntimeError (or ValueError) telling the user to install pandas, and
proceed to use pd as before—this localizes the dependency to callers of
beautiful_df_print and prevents breaking imports for other users.

Comment on lines +150 to +155
else :
per_class_array = {"precision": precision, "recall": recall, "f_score": f_score}
macro_precision = np.round(precision.sum()/len(precision),decimals=2)
macro_recall = np.round(recall.sum()/len(recall),decimals=2)
macro_f_score = np.round(f_score.sum()/ len(f_score), decimals=2)
return per_class_array,{"precision": macro_precision, "recall": macro_recall, "f_score": macro_f_score}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle empty label sets to avoid ZeroDivisionError.
If there are no labels (no true/pred entities), len(precision) is 0 and macro aggregation crashes. Please return zeros (or NaNs) in this edge case.

🛠️ Suggested guard for empty metrics
     else : 
         per_class_array = {"precision": precision, "recall": recall, "f_score": f_score}
-        macro_precision = np.round(precision.sum()/len(precision),decimals=2)
-        macro_recall = np.round(recall.sum()/len(recall),decimals=2)
-        macro_f_score = np.round(f_score.sum()/ len(f_score), decimals=2)
+        if len(precision) == 0:
+            return per_class_array, {"precision": 0.0, "recall": 0.0, "f_score": 0.0}
+        macro_precision = np.round(precision.sum()/len(precision),decimals=2)
+        macro_recall = np.round(recall.sum()/len(recall),decimals=2)
+        macro_f_score = np.round(f_score.sum()/ len(f_score), decimals=2)
         return per_class_array,{"precision": macro_precision, "recall": macro_recall, "f_score": macro_f_score}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else :
per_class_array = {"precision": precision, "recall": recall, "f_score": f_score}
macro_precision = np.round(precision.sum()/len(precision),decimals=2)
macro_recall = np.round(recall.sum()/len(recall),decimals=2)
macro_f_score = np.round(f_score.sum()/ len(f_score), decimals=2)
return per_class_array,{"precision": macro_precision, "recall": macro_recall, "f_score": macro_f_score}
else :
per_class_array = {"precision": precision, "recall": recall, "f_score": f_score}
if len(precision) == 0:
return per_class_array, {"precision": 0.0, "recall": 0.0, "f_score": 0.0}
macro_precision = np.round(precision.sum()/len(precision),decimals=2)
macro_recall = np.round(recall.sum()/len(recall),decimals=2)
macro_f_score = np.round(f_score.sum()/ len(f_score), decimals=2)
return per_class_array,{"precision": macro_precision, "recall": macro_recall, "f_score": macro_f_score}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gliner/evaluation/evaluator.py` around lines 150 - 155, The current macro
aggregation divides by len(precision) which raises ZeroDivisionError when there
are no labels; inside the same else branch guard on an empty metrics array
(e.g., if len(precision) == 0 or precision.size == 0) and set macro_precision,
macro_recall, macro_f_score to 0.0 (or np.nan if you prefer) instead of
computing the division, then return per_class_array (which may be empty) and the
macro dict; reference the variables precision, recall, f_score and the returned
per_class_array to locate where to add the guard.

Comment on lines +188 to +201
"""
output : {

"per_class":{"tag1":{"precision":int, "recall":int,"f_score":int},
"tag2":{}...
},
"micro":{"precision":int, "recall":int,"f_score":int},
"macro":{"precision":int, "recall":int,"f_score":int},
}
"""
all_true_typed, all_outs_typed = self.transform_data()
precision, recall, f1 = compute_prf(all_true_typed, all_outs_typed).values()
output_str = f"P: {precision:.2%}\tR: {recall:.2%}\tF1: {f1:.2%}\n"
return output_str, f1
results_all = compute_prf(all_true_typed, all_outs_typed)
# output_str = f"P: {precision:.2%}\tR: {recall:.2%}\tF1: {f1:.2%}\n"
return results_all

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update the docstring to reflect float metrics and dict output.
The new return value is a dict of rounded floats, but the docstring currently documents ints.

✏️ Docstring tweak
-        "per_class":{"tag1":{"precision":int, "recall":int,"f_score":int},
+        "per_class":{"tag1":{"precision":float, "recall":float,"f_score":float},
                     "tag2":{}...
                     },
-        "micro":{"precision":int, "recall":int,"f_score":int},
-        "macro":{"precision":int, "recall":int,"f_score":int},
+        "micro":{"precision":float, "recall":float,"f_score":float},
+        "macro":{"precision":float, "recall":float,"f_score":float},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"""
output : {
"per_class":{"tag1":{"precision":int, "recall":int,"f_score":int},
"tag2":{}...
},
"micro":{"precision":int, "recall":int,"f_score":int},
"macro":{"precision":int, "recall":int,"f_score":int},
}
"""
all_true_typed, all_outs_typed = self.transform_data()
precision, recall, f1 = compute_prf(all_true_typed, all_outs_typed).values()
output_str = f"P: {precision:.2%}\tR: {recall:.2%}\tF1: {f1:.2%}\n"
return output_str, f1
results_all = compute_prf(all_true_typed, all_outs_typed)
# output_str = f"P: {precision:.2%}\tR: {recall:.2%}\tF1: {f1:.2%}\n"
return results_all
"""
output : {
"per_class":{"tag1":{"precision":float, "recall":float,"f_score":float},
"tag2":{}...
},
"micro":{"precision":float, "recall":float,"f_score":float},
"macro":{"precision":float, "recall":float,"f_score":float},
}
"""
all_true_typed, all_outs_typed = self.transform_data()
results_all = compute_prf(all_true_typed, all_outs_typed)
# output_str = f"P: {precision:.2%}\tR: {recall:.2%}\tF1: {f1:.2%}\n"
return results_all
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gliner/evaluation/evaluator.py` around lines 188 - 201, The docstring for the
method currently claims integer metrics but the method returns a dict of rounded
floats (the variable results_all returned from compute_prf after
transform_data). Update the docstring to state that the return value is a dict
containing per_class, micro and macro entries with float
precision/recall/f_score (rounded floats), and show the exact dict structure and
example types (e.g. "precision": float) to match compute_prf/results_all;
mention transform_data and compute_prf in the description so readers can trace
the data flow.

Comment thread gliner/model.py
import os
import re
import warnings
import pandas as pd

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate common dependency manifests and check for pandas.
echo "=== Searching for dependency manifests and pandas ==="
files=$(find . -maxdepth 3 -type f \( -name 'requirements*.txt' -o -name 'pyproject.toml' -o -name 'setup.cfg' -o -name 'setup.py' -o -name 'Pipfile' -o -name 'environment.yml' \) 2>/dev/null)
if [ -n "$files" ]; then
  echo "Found manifest files:"
  echo "$files"
  echo ""
  echo "=== Checking for pandas in these files ==="
  rg -n 'pandas' $files
else
  echo "No dependency manifest files found."
fi

Repository: arthrod/GLiNER

Length of output: 213


🏁 Script executed:

#!/bin/bash
# Check the actual import in gliner/model.py and the beautiful_df_print function
echo "=== First 20 lines of gliner/model.py ==="
head -20 gliner/model.py

echo ""
echo "=== Lines 663-676 of gliner/model.py (beautiful_df_print function) ==="
sed -n '663,676p' gliner/model.py

echo ""
echo "=== Check for all imports of pandas in gliner/model.py ==="
rg -n 'import.*pandas\|from.*pandas' gliner/model.py

Repository: arthrod/GLiNER

Length of output: 1318


🏁 Script executed:

#!/bin/bash
echo "=== pyproject.toml ==="
cat pyproject.toml

echo ""
echo "=== requirements.txt ==="
cat requirements.txt

Repository: arthrod/GLiNER

Length of output: 1533


Add pandas to dependencies or move the import inside beautiful_df_print.

Pandas is imported at module scope (line 5) but not declared in dependencies in pyproject.toml or requirements.txt. This breaks module loading for any user without pandas installed, even if they never call beautiful_df_print. Either add "pandas" to the dependencies list in pyproject.toml, or move the import inside the method with a helpful error message if pandas is not available.

Option: lazy-import inside the method
 def beautiful_df_print(self, eval_results:Dict):
+        try:
+            import pandas as pd
+        except ModuleNotFoundError as exc:
+            raise ModuleNotFoundError(
+                "pandas is required for beautiful_df_print. Please install it (e.g., pip install pandas)."
+            ) from exc
         """
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gliner/model.py` at line 5, The module-level "import pandas as pd" causes
import errors for users without pandas; either add "pandas" to project
dependencies in pyproject.toml/requirements.txt, or lazy-import it inside the
function beautiful_df_print in gliner.model: remove the top-level import, add
"import pandas as pd" at the start of beautiful_df_print, catch ImportError and
raise a clear RuntimeError (or ValueError) telling the user to install pandas,
and proceed to use pd as before—this localizes the dependency to callers of
beautiful_df_print and prevents breaking imports for other users.

Comment thread gliner/model.py
Comment on lines +656 to +661
# out, f1 = evaluator.evaluate()

return out, f1
# return out, f1
all_results = evaluator.evaluate()

return all_results

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update evaluate() docs to match the new return type.
The function now returns a dict (not a tuple with F1), so the docstring should be updated accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gliner/model.py` around lines 656 - 661, The docstring for the evaluate()
method no longer matches its return type; update the evaluate() docstring (the
one for the Evaluator.evaluate / evaluate() method referenced by
evaluator.evaluate and the caller using all_results) to state that it returns a
dict of metrics/results (not a tuple with F1), describe the dict keys and value
types (e.g., overall F1, per-class metrics, any other entries), and remove any
mention of returning a tuple (out, f1) so callers and readers understand the new
return structure.

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.

1 participant