项目文件夹

文件
2026-07-13 13:22:34 +08:00

191 行
5.7 KiB
Python

import json
from unittest.mock import MagicMock, patch
from mlflow.entities.issue import IssueSeverity, IssueStatus
from mlflow.genai.discovery.clustering import (
cluster_by_llm,
summarize_cluster,
)
from mlflow.genai.discovery.constants import build_cluster_summary_prompt
from mlflow.genai.discovery.entities import (
Issue,
_ConversationAnalysis,
)
from mlflow.genai.discovery.utils import build_summary
# ---- cluster_by_llm ----
def test_cluster_by_llm_groups_similar():
labels = [
"[llm_pipeline] hallucinated facts",
"[llm_pipeline] hallucinated different facts",
"[database] query timeout",
]
mock_response = MagicMock()
mock_response.choices = [
MagicMock(
message=MagicMock(
content=json.dumps({
"groups": [
{"name": "Issue: Hallucination", "indices": [0, 1]},
{"name": "Issue: Query timeout", "indices": [2]},
]
})
)
)
]
with patch("litellm.completion", return_value=mock_response) as mock_completion:
groups = cluster_by_llm(labels, max_issues=5, model="openai:/gpt-5")
mock_completion.assert_called_once()
assert len(groups) == 2
flat = [idx for g in groups for idx in g]
assert sorted(flat) == [0, 1, 2]
def test_cluster_by_llm_respects_max_issues():
labels = [f"[domain_{i}] unique issue {i}" for i in range(5)]
mock_response = MagicMock()
mock_response.choices = [
MagicMock(
message=MagicMock(
content=json.dumps({
"groups": [
{"name": "Issue: Group A", "indices": [0, 1, 2]},
{"name": "Issue: Group B", "indices": [3, 4]},
]
})
)
)
]
with patch("litellm.completion", return_value=mock_response) as mock_completion:
groups = cluster_by_llm(labels, max_issues=2, model="openai:/gpt-5")
mock_completion.assert_called_once()
assert len(groups) <= 2
# ---- summarize_cluster ----
def test_summarize_cluster():
analyses = [
_ConversationAnalysis(
full_rationale="response generation via LLM",
affected_trace_ids=["t-1"],
),
_ConversationAnalysis(
full_rationale="response generation via LLM",
affected_trace_ids=["t-2"],
),
]
mock_response = MagicMock()
mock_response.choices = [
MagicMock(
message=MagicMock(
content=json.dumps({
"name": "hallucination",
"description": "LLM generates incorrect facts",
"root_cause": "Model confabulation",
"example_indices": [],
"severity": "high",
"categories": [],
})
)
)
]
with patch(
"mlflow.genai.discovery.clustering._call_llm", return_value=mock_response
) as mock_call:
result = summarize_cluster([0, 1], analyses, "openai:/gpt-5", categories=[])
mock_call.assert_called_once()
assert result.name == "hallucination"
assert result.example_indices == [0, 1]
# ---- build_summary ----
def test_build_summary_no_issues():
summary = build_summary([], 50)
assert "50 traces" in summary
assert "No issues found" in summary
def test_build_summary_with_issues():
issues = [
Issue(
issue_id="test-id",
experiment_id="0",
name="tool_failure",
description="Tool calls fail intermittently",
status=IssueStatus.PENDING,
created_timestamp=0,
last_updated_timestamp=0,
severity=IssueSeverity.HIGH,
root_causes=["API timeout"],
),
]
summary = build_summary(issues, 100)
assert "tool_failure" in summary
assert "API timeout" in summary
def test_summarize_cluster_filters_invalid_categories():
analyses = [
_ConversationAnalysis(
full_rationale="[hallucination] agent made up facts",
affected_trace_ids=["t-1"],
),
_ConversationAnalysis(
full_rationale="[tool_error] tool call failed",
affected_trace_ids=["t-2"],
),
]
mock_response = MagicMock()
mock_response.choices = [
MagicMock(
message=MagicMock(
content=json.dumps({
"name": "Issue: Multiple problems",
"description": "Various issues detected",
"root_cause": "Multiple root causes",
"example_indices": [],
"severity": "high",
"categories": ["hallucination", "invalid_cat", "tool_error", "another_invalid"],
})
)
)
]
valid_categories = ["hallucination", "tool_error", "latency"]
with patch(
"mlflow.genai.discovery.clustering._call_llm", return_value=mock_response
) as mock_call:
result = summarize_cluster([0, 1], analyses, "openai:/gpt-5", categories=valid_categories)
mock_call.assert_called_once()
assert set(result.categories) == {"hallucination", "tool_error"}
assert "invalid_cat" not in result.categories
assert "another_invalid" not in result.categories
def test_build_cluster_summary_prompt_with_categories():
categories = ["hallucination", "tool_error", "latency"]
prompt = build_cluster_summary_prompt(categories=categories)
assert "hallucination" in prompt
assert "tool_error" in prompt
assert "latency" in prompt
assert "Assign one or more categories from" in prompt