Skip to content

Commit 6efda04

Browse files
stretpjcAbhiPrasad
andauthored
fix: eval parameters omit null description (#613)
## Summary Remote eval dev mode (`braintrust eval <file> --dev`) hid **all** evals in the playground UI when any eval declared a parameter without a `description`. All three branches of `serialize_eval_parameters` (prompt, model, data) emitted `"description": null` when unset. The playground's zod schema only accepts a string or an absent key, and it validates the whole `/list` response in one `safeParse` — so one description-less parameter hid every eval on the endpoint. Fix: include `description` only when it's not `None`, same as `default` already is. #508 fixed this pattern for prompt defaults; `description` was missed. ## Test With proposed change the remote eval is detected: <img width="457" height="291" alt="Screenshot 2026-07-22 at 2 13 25 PM" src="https://github.com/user-attachments/assets/ade8ce11-2843-4b25-a291-dc6a447671e0" /> Same remote eval running in 0.30.1 is not detected: <img width="1746" height="600" alt="image" src="https://github.com/user-attachments/assets/47409355-fcf2-4c29-8de9-1aeaa6b99099" /> --------- Co-authored-by: Abhijeet Prasad <abhijeet@braintrustdata.com>
1 parent 079699a commit 6efda04

2 files changed

Lines changed: 59 additions & 13 deletions

File tree

py/src/braintrust/parameters.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .generated_types import PromptBlockDataNullish, PromptOptionsNullish
1313
from .prompt import PromptData
1414
from .serializable_data_class import SerializableDataClass
15+
from .util import clean_nones
1516

1617

1718
if TYPE_CHECKING:
@@ -424,19 +425,23 @@ def serialize_eval_parameters(parameters: EvalParameters) -> dict[str, Any]:
424425

425426
for name, schema in parameters.items():
426427
if _is_prompt_parameter(schema):
427-
prompt_parameter_data: dict[str, Any] = {
428-
"type": "prompt",
429-
"description": schema.get("description"),
430-
}
428+
prompt_parameter_data = clean_nones(
429+
{
430+
"type": "prompt",
431+
"description": schema.get("description"),
432+
}
433+
)
431434
prompt_default = schema.get("default")
432435
if prompt_default is not None:
433436
prompt_parameter_data["default"] = _prompt_data_to_dict(prompt_default)
434437
result[name] = prompt_parameter_data
435438
elif _is_model_parameter(schema):
436-
model_parameter_data: dict[str, Any] = {
437-
"type": "model",
438-
"description": schema.get("description"),
439-
}
439+
model_parameter_data = clean_nones(
440+
{
441+
"type": "model",
442+
"description": schema.get("description"),
443+
}
444+
)
440445
model_default = schema.get("default")
441446
if model_default is not None:
442447
model_parameter_data["default"] = model_default
@@ -448,11 +453,13 @@ def serialize_eval_parameters(parameters: EvalParameters) -> dict[str, Any]:
448453
}
449454
else:
450455
schema_json = _serialize_pydantic_parameter_schema(schema)
451-
data_parameter_data: dict[str, Any] = {
452-
"type": "data",
453-
"schema": schema_json,
454-
"description": schema_json.get("description"),
455-
}
456+
data_parameter_data = clean_nones(
457+
{
458+
"type": "data",
459+
"schema": schema_json,
460+
"description": schema_json.get("description"),
461+
}
462+
)
456463
if "default" in schema_json:
457464
data_parameter_data["default"] = schema_json["default"]
458465
result[name] = data_parameter_data

py/src/braintrust/test_parameters.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,3 +635,42 @@ def test_prompt_parameter_defaults_omit_none_values_from_dict():
635635
assert "function_call" not in default["prompt"]["messages"][0]
636636
assert "tool_calls" not in default["prompt"]["messages"][0]
637637
_assert_no_none_values(default)
638+
639+
640+
def test_serialize_eval_parameters_omits_description_when_absent():
641+
class _FakeField:
642+
required = False
643+
644+
class TemplateParam:
645+
__fields__ = {"value": _FakeField()}
646+
647+
@classmethod
648+
def parse_obj(cls, value):
649+
return value
650+
651+
@classmethod
652+
def schema(cls):
653+
return {
654+
"type": "object",
655+
"properties": {
656+
"value": {
657+
"type": "string",
658+
"default": "default template",
659+
}
660+
},
661+
}
662+
663+
serialized = serialize_eval_parameters(
664+
{
665+
"template": TemplateParam,
666+
"main": {"type": "prompt"},
667+
"judge_model": {"type": "model"},
668+
}
669+
)
670+
671+
assert serialized["template"]["type"] == "data"
672+
assert serialized["template"]["default"] == "default template"
673+
assert serialized["main"]["type"] == "prompt"
674+
assert serialized["judge_model"]["type"] == "model"
675+
for name in ("template", "main", "judge_model"):
676+
assert "description" not in serialized[name]

0 commit comments

Comments
 (0)