Files
2026-07-13 13:32:05 +08:00

151 lines
5.3 KiB
Python

from pydantic import BaseModel, Field
from typing import Optional, List, Union, Dict
from deepeval.test_case import MLLMImage, ToolCall
from deepeval.tracing.api import TraceApi, MetricData
from deepeval.utils import make_model_config
class LLMApiTestCase(BaseModel):
name: str
input: str
actual_output: Optional[str] = Field(None, alias="actualOutput")
expected_output: Optional[str] = Field(None, alias="expectedOutput")
context: Optional[list] = Field(None)
retrieval_context: Optional[list] = Field(None, alias="retrievalContext")
tools_called: Optional[list] = Field(None, alias="toolsCalled")
expected_tools: Optional[list] = Field(None, alias="expectedTools")
token_cost: Optional[float] = Field(None, alias="tokenCost")
completion_time: Optional[float] = Field(None, alias="completionTime")
tags: Optional[List[str]] = Field(None)
# multimodal_input: Optional[str] = Field(None, alias="multimodalInput")
# multimodal_input_actual_output: Optional[str] = Field(
# None, alias="multimodalActualOutput"
# )
# multimodal_expected_output: Optional[str] = Field(
# None, alias="multimodalExpectedOutput"
# )
# multimodal_retrieval_context: Optional[List[str]] = Field(
# None, alias="multimodalRetrievalContext"
# )
# multimodal_context: Optional[List[str]] = Field(
# None, alias="multimodalContext"
# )
images_mapping: Optional[Dict[str, MLLMImage]] = Field(
None, alias="imagesMapping"
)
# make these optional, not all test cases in a conversation will be evaluated
success: Union[bool, None] = Field(None)
metrics_data: Union[List[MetricData], None] = Field(
None, alias="metricsData"
)
run_duration: Union[float, None] = Field(None, alias="runDuration")
evaluation_cost: Union[float, None] = Field(None, alias="evaluationCost")
order: Union[int, None] = Field(None)
# These should map 1 to 1 from golden
metadata: Optional[Dict] = Field(None)
comments: Optional[str] = Field(None)
trace: Optional[TraceApi] = Field(None)
model_config = make_model_config(arbitrary_types_allowed=True)
# metric_collection: Optional[str] = Field(None, alias="metricCollection")
def update_metric_data(self, metric_data: MetricData):
if self.metrics_data is None:
self.metrics_data = [metric_data]
else:
self.metrics_data.append(metric_data)
if self.success is None:
# self.success will be None when it is a message
# in that case we will be setting success for the first time
self.success = metric_data.success
else:
if metric_data.success is False:
self.success = False
evaluationCost = metric_data.evaluation_cost
if evaluationCost is None:
return
if self.evaluation_cost is None:
self.evaluation_cost = evaluationCost
else:
self.evaluation_cost += evaluationCost
def update_run_duration(self, run_duration: float):
self.run_duration = run_duration
def update_status(self, success: bool):
if self.success is None:
self.success = success
else:
if success is False:
self.success = False
def is_multimodal(self):
if (
self.multimodal_input is not None
and self.multimodal_input_actual_output is not None
):
return True
return False
class TurnApi(BaseModel):
role: str
content: str
order: int
user_id: Optional[str] = Field(None, alias="userId")
retrieval_context: Optional[list] = Field(None, alias="retrievalContext")
tools_called: Optional[List[ToolCall]] = Field(None, alias="toolsCalled")
comments: Optional[str] = Field(None)
class ConversationalApiTestCase(BaseModel):
name: str
success: bool
metrics_data: List[MetricData] = Field(alias="metricsData")
run_duration: float = Field(0.0, alias="runDuration")
evaluation_cost: Union[float, None] = Field(None, alias="evaluationCost")
turns: List[TurnApi] = Field(default_factory=lambda: [])
order: Union[int, None] = Field(None)
scenario: Optional[str] = Field(None)
expected_outcome: Optional[str] = Field(None, alias="expectedOutcome")
user_description: Optional[str] = Field(None, alias="userDescription")
context: Optional[list] = Field(None)
comments: Optional[str] = Field(None)
metadata: Optional[Dict] = Field(None)
images_mapping: Optional[Dict[str, MLLMImage]] = Field(
None, alias="imagesMapping"
)
tags: Optional[List[str]] = Field(None)
def update_metric_data(self, metrics_data: MetricData):
if self.metrics_data is None:
self.metrics_data = [metrics_data]
else:
self.metrics_data.append(metrics_data)
if metrics_data.success is False:
self.success = False
evaluationCost = metrics_data.evaluation_cost
if evaluationCost is None:
return
if self.evaluation_cost is None:
self.evaluation_cost = evaluationCost
else:
self.evaluation_cost += evaluationCost
def update_run_duration(self, run_duration: float):
self.run_duration += run_duration
class TestRunHttpResponse(BaseModel):
id: str