Skip to content

Fix InstructionsJudge using scorer description as assessment value#19121

Merged
alkispoly-db merged 5 commits intomlflow:masterfrom
alkispoly-db:scorer-bug
Dec 1, 2025
Merged

Fix InstructionsJudge using scorer description as assessment value#19121
alkispoly-db merged 5 commits intomlflow:masterfrom
alkispoly-db:scorer-bug

Conversation

@alkispoly-db
Copy link
Collaborator

@alkispoly-db alkispoly-db commented Nov 30, 2025

🛠 DevTools 🛠

Open in GitHub Codespaces

Install mlflow from this PR

# mlflow
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/19121/merge
# mlflow-skinny
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/19121/merge#subdirectory=libs/skinny

For Databricks, use the following command:

%sh curl -LsSf https://raw.githubusercontent.com/mlflow/mlflow/HEAD/dev/install-skinny.sh | sh -s pull/19121/merge

Related Issues/PRs

N/A - Bug fix discovered during development

What changes are proposed in this pull request?

This PR fixes a bug where InstructionsJudge scorers with custom descriptions would cause the LLM to echo back the scorer's description as the assessment value instead of generating an actual evaluation rating.

Root Cause:
The scorer's description (e.g., "Evaluates if the answer is concise") was being used in:

  1. The JSON schema (response_format) sent to the LLM
  2. The system prompt instructions

This caused the LLM to interpret the description as what the "result" field should contain, leading it to echo the description instead of generating an assessment.

Solution:

  • Use generic field description (_RESULT_FIELD_DESCRIPTION = "The evaluation rating/result") instead of the scorer's description
  • Refactored _create_response_format_model() to use get_output_fields() as the single source of truth for field definitions
  • Added explanatory comments to prevent future regressions

Example Impact:

  • Before: FeedbackValue(value="Evaluates if the answer is concise")
  • After: FeedbackValue(value=4)

Files Changed:

  • mlflow/genai/judges/instructions_judge/__init__.py: Fixed field description logic in two methods
  • tests/genai/judges/test_make_judge.py: Added comprehensive test coverage

How is this PR tested?

  • Existing unit/integration tests (all pass)
  • New unit/integration tests
    • test_response_format_uses_generic_description_when_scorer_has_description
    • test_response_format_uses_generic_description_when_scorer_has_no_description
  • Manual tests (verified with Databricks integration - scorer now returns numeric ratings instead of description strings)

Does this PR require documentation update?

  • No. You can skip the rest of this section.

Release Notes

Is this a user-facing change?

  • Yes. Give a description of this change to be included in the release notes for MLflow users.

Description: Fixed a bug where LLM judge scorers (InstructionsJudge) with custom descriptions would return the description text as the assessment value instead of generating actual evaluation ratings. Scorers now correctly return numeric or categorical assessments as intended.

What component(s), interfaces, languages, and integrations does this PR affect?

Components

  • area/evaluation: MLflow model evaluation features, evaluation metrics, and evaluation workflows
  • area/tracing: MLflow Tracing features, tracing APIs, and LLM tracing functionality

How should the PR be classified in the release notes? Choose one:

  • rn/bug-fix - A user-facing bug fix worth mentioning in the release notes

Should this PR be included in the next patch release?

  • Yes (this PR will be cherry-picked and included in the next patch release)

Rationale: This is a bug fix that affects judge/scorer functionality. Users relying on custom-described judges are getting incorrect assessment values.

When InstructionsJudge scorers had custom descriptions, the LLM would
echo back the scorer's description as the assessment value instead of
generating an actual evaluation rating.

Root cause: The scorer's description was being used in both the JSON
schema (response_format) and system prompt instructions, causing the
LLM to interpret it as what the result field should contain.

Fix: Use generic field descriptions (_RESULT_FIELD_DESCRIPTION) instead
of the scorer's description. Refactored to use get_output_fields() as
the single source of truth for field definitions.

Example:
- Before: value="Evaluates if the answer is concise" (description string)
- After: value=4 (actual rating)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Alkis Polyzotis <alkis.polyzotis@databricks.com>
@github-actions github-actions bot added v3.7.0 area/evaluation MLflow Evaluation area/tracing MLflow Tracing and its integrations rn/bug-fix Mention under Bug Fixes in Changelogs. labels Nov 30, 2025
alkispoly-db and others added 2 commits November 30, 2025 23:49
Simplify test function docstrings per project guidelines to avoid
redundant documentation that merely repeats what the test does.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Alkis Polyzotis <alkis.polyzotis@databricks.com>
Remove explanatory comments for cleaner code per project style.
The implementation is self-documenting: get_output_fields() is
clearly the single source of truth.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Alkis Polyzotis <alkis.polyzotis@databricks.com>
@github-actions
Copy link
Contributor

github-actions bot commented Dec 1, 2025

Documentation preview for 4d1a2da is available at:

More info
  • Ignore this comment if this PR does not change the documentation.
  • The preview is updated when a new commit is pushed to this PR.
  • This comment was created by this workflow run.
  • The documentation was built by this workflow run.


fields = (
{"rationale": rationale_field, "result": result_field}
if self._generate_rationale_first
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we keep the _generate_rationale_first logic?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My bad -- I will add back the logic for this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Actually, this logic is now refactored into get_output_fields(), so it is correctly maintained.

@TomeHirata
Copy link
Collaborator

TomeHirata commented Dec 1, 2025

I'm curious which model causes the echo. I tested the following code using GPT, and it correctly returns the eval result without echoing. Also, did it happen even when feedback_value_type is specified?

from mlflow.genai import make_judge

judge = make_judge(
    name="conciseness", 
    instructions="the response {{outputs}} is concise enough", 
    description="Evaluates if the answer is concise",
    model="openai:/gpt-5-mini")
result = judge(outputs="The capital of France is Paris")
result.value
-> "Yes"

# NOT the scorer's description
result_description = schema["properties"]["result"]["description"]
assert result_description == _RESULT_FIELD_DESCRIPTION, (
f"Response format should use generic field description, not scorer description.\n"
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: probably we don't need assertion message

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agred -- will remove.

output_fields = judge.get_output_fields()
result_field = next(f for f in output_fields if f.name == "result")
assert result_field.description == _RESULT_FIELD_DESCRIPTION, (
f"Output fields should use generic description in system prompt.\n"
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: ditto

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Copy link
Collaborator

@TomeHirata TomeHirata left a comment

Choose a reason for hiding this comment

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

Left some questions/comments. But agree to use hardcoded result field description to get constant outputs.

assert output_fields_rationale_first[1].value_type == Literal["good", "bad"] # result


def test_response_format_uses_generic_description_when_scorer_has_description():
Copy link
Collaborator

@TomeHirata TomeHirata Dec 1, 2025

Choose a reason for hiding this comment

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

Can we combine the two tests? The logic is identical except for the description field.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea -- done.

Combine test_response_format_uses_generic_description_when_scorer_has_description
and test_response_format_uses_generic_description_when_scorer_has_no_description
into a single parameterized test for better maintainability.

The parameterized approach tests both scenarios (with and without custom
description) using the same test logic, reducing code duplication.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Alkis Polyzotis <alkis.polyzotis@databricks.com>
@alkispoly-db
Copy link
Collaborator Author

I'm curious which model causes the echo. I tested the following code using GPT, and it correctly returns the eval result without echoing. Also, did it happen even when feedback_value_type is specified?

from mlflow.genai import make_judge

judge = make_judge(
    name="conciseness", 
    instructions="the response {{outputs}} is concise enough", 
    description="Evaluates if the answer is concise",
    model="openai:/gpt-5-mini")
result = judge(outputs="The capital of France is Paris")
result.value
-> "Yes"

It happened with gpt4o-mini which is the current model used in Databricks.

@alkispoly-db alkispoly-db added this pull request to the merge queue Dec 1, 2025
Merged via the queue into mlflow:master with commit 3c22da7 Dec 1, 2025
46 checks passed
@alkispoly-db alkispoly-db deleted the scorer-bug branch December 1, 2025 02:40
BenWilson2 pushed a commit to BenWilson2/mlflow that referenced this pull request Dec 4, 2025
…lflow#19121)

Signed-off-by: Alkis Polyzotis <alkis.polyzotis@databricks.com>
Co-authored-by: Claude <noreply@anthropic.com>
BenWilson2 pushed a commit that referenced this pull request Dec 4, 2025
…19121)

Signed-off-by: Alkis Polyzotis <alkis.polyzotis@databricks.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/evaluation MLflow Evaluation area/tracing MLflow Tracing and its integrations rn/bug-fix Mention under Bug Fixes in Changelogs. v3.7.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants