@@ -157,6 +157,51 @@ def validate_parameters(
157157 return result
158158
159159
160+ def _extract_pydantic_default_and_description (
161+ model : Any , schema_obj : dict [str , Any ]
162+ ) -> tuple [Any , str | None ]:
163+ """Extract default value and description from a pydantic model's JSON schema.
164+
165+ In the JS SDK, Zod's zodToJsonSchema() puts `default` and `description` at the
166+ top level of the generated schema (e.g. z.string().default("x").describe("y")
167+ produces {"type": "string", "default": "x", "description": "y"}). This makes
168+ it trivial to read them off the schema object (see
169+ serializeEvalParametersToStaticParametersSchema in sdk/js/src/framework2.ts).
170+
171+ Pydantic works differently. For the single-field wrapper pattern we use
172+ (class Param(BaseModel): value: T = default), the default and description end up
173+ nested inside properties.value rather than at the top level. For multi-field models,
174+ pydantic doesn't emit a top-level default at all — we have to instantiate the model
175+ to extract it.
176+ """
177+ fields = getattr (model , "__fields__" , None ) or getattr (model , "model_fields" , {})
178+ is_single_value = isinstance (fields , dict ) and len (fields ) == 1 and "value" in fields
179+
180+ if is_single_value :
181+ value_schema = schema_obj .get ("properties" , {}).get ("value" , {})
182+ default = value_schema .get ("default" )
183+ description = value_schema .get ("description" )
184+ if default is None :
185+ try :
186+ instance = model ()
187+ raw = getattr (instance , "value" )
188+ default = raw .model_dump () if hasattr (raw , "model_dump" ) else (raw .dict () if hasattr (raw , "dict" ) else raw )
189+ except Exception :
190+ pass
191+ return default , description
192+
193+ default = schema_obj .get ("default" )
194+ description = schema_obj .get ("description" )
195+ if default is None :
196+ try :
197+ instance = model ()
198+ default = instance .model_dump () if hasattr (instance , "model_dump" ) else instance .dict ()
199+ except Exception :
200+ pass
201+
202+ return default , description
203+
204+
160205def parameters_to_json_schema (parameters : EvalParameters | RemoteEvalParameters | dict | None ) -> dict [str , Any ]:
161206 """
162207 Convert EvalParameters to JSON schema format for serialization.
@@ -198,22 +243,23 @@ def parameters_to_json_schema(parameters: EvalParameters | RemoteEvalParameters
198243
199244 for name , schema in parameters .items ():
200245 if isinstance (schema , dict ) and schema .get ("type" ) == "prompt" :
201- # Prompt parameter
202246 result [name ] = {
203247 "type" : "prompt" ,
204248 "default" : schema .get ("default" ),
205249 "description" : schema .get ("description" ),
206250 }
207251 else :
208- # Pydantic model
209252 try :
210- result [name ] = {
211- "type" : "data" ,
212- "schema" : _pydantic_to_json_schema (schema ),
213- # TODO: Extract default and description from pydantic model
214- }
253+ schema_obj = _pydantic_to_json_schema (schema )
215254 except ValueError :
216- # Not a pydantic model, skip
217- pass
255+ continue
256+
257+ default , description = _extract_pydantic_default_and_description (schema , schema_obj )
258+ entry : dict [str , Any ] = {"type" : "data" , "schema" : schema_obj }
259+ if default is not None :
260+ entry ["default" ] = default
261+ if description is not None :
262+ entry ["description" ] = description
263+ result [name ] = entry
218264
219265 return result
0 commit comments