33from typing import Any , Dict , List , Optional , Union
44
55from ..models import EvaluateResult , Message , MetricResult
6+ from ._content_utils import to_text , to_text_any
67from ..typed_interface import reward_function
78from .function_calling import (
89 calculate_jaccard_similarity ,
@@ -54,7 +55,7 @@ def json_schema_reward(
5455
5556 if isinstance (last_message , Message ):
5657 if last_message .role == "assistant" and last_message .content is not None :
57- content_text = last_message .content
58+ content_text = to_text ( last_message .content )
5859 else :
5960 return EvaluateResult (
6061 score = 0.0 ,
@@ -69,7 +70,7 @@ def json_schema_reward(
6970 )
7071 elif isinstance (last_message , dict ):
7172 if last_message .get ("role" ) == "assistant" and last_message .get ("content" ) is not None :
72- content_text = last_message .get ("content" , "" )
73+ content_text = to_text_any ( last_message .get ("content" , "" ) )
7374 else :
7475 return EvaluateResult (
7576 score = 0.0 ,
@@ -260,7 +261,7 @@ def json_schema_reward_with_llm_judge(
260261 """
261262 # Import OpenAI at call time to make this optional
262263 try :
263- from openai import OpenAI
264+ from openai import OpenAI # type: ignore[reportMissingImports]
264265 except ImportError :
265266 return EvaluateResult (
266267 score = 0.0 ,
@@ -280,7 +281,7 @@ def json_schema_reward_with_llm_judge(
280281 total_weight = sum (weights .values ())
281282 normalized_weights = {k : v / total_weight for k , v in weights .items ()}
282283
283- schema_result = json_schema_reward (
284+ schema_result = json_schema_reward ( # type: ignore[reportCallIssue]
284285 messages = messages ,
285286 ground_truth = ground_truth ,
286287 json_content = json_content ,
@@ -295,7 +296,10 @@ def json_schema_reward_with_llm_judge(
295296 if "error" in schema_result .metrics :
296297 return schema_result
297298 last_message = messages [- 1 ]
298- content = last_message .get ("content" , "" )
299+ if isinstance (last_message , Message ):
300+ content = to_text (last_message .content )
301+ else :
302+ content = to_text_any (last_message .get ("content" , "" ))
299303 json_str_from_msg = ""
300304 try :
301305 pattern = r"```(?:json)?\s*([\s\S]*?)```"
@@ -308,115 +312,20 @@ def json_schema_reward_with_llm_judge(
308312 json_str_from_msg = json_matches [0 ]
309313 except Exception :
310314 pass
311- try :
312- if json_str_from_msg :
313- json_content = json .loads (json_str_from_msg )
314- except json .JSONDecodeError :
315- json_content = json_str_from_msg
316-
317- if isinstance (json_content , dict ):
318- json_str_for_llm = json .dumps (json_content , indent = 2 )
319- else :
320- json_str_for_llm = str (json_content )
321-
322- expected_schema_str = json .dumps (expected_schema , indent = 2 ) if expected_schema else "No schema provided"
323-
324- conversation_msg = "No conversation context provided"
325- if messages :
326- conversation_parts = []
327- for msg in messages [:- 1 ]:
328- role = msg .get ("role" , "" )
329- content_part = msg .get ("content" , "" )
330- if role and content_part :
331- conversation_parts .append (f"{ role } : { content_part } " )
332- if conversation_parts :
333- conversation_msg = "\n " .join (conversation_parts )
334-
335- prompt = f"""You are evaluating the quality of JSON content provided by an AI assistant.
336- Your job is to assess whether the JSON structure and content is appropriate, correctly formatted,
337- and follows the expected schema and behavior.
338-
339- CONVERSATION CONTEXT:
340- { conversation_msg }
341-
342- JSON CONTENT:
343- { json_str_for_llm }
344-
345- EXPECTED SCHEMA:
346- { expected_schema_str }
347-
348- EXPECTED BEHAVIOR/CONTENT:
349- { expected_behavior }
350-
351- Evaluate the JSON content and provide:
352- 1. A score from 0.0 to 1.0 (where 1.0 is perfect)
353- 2. A detailed explanation of your rating
354- 3. Specific issues or strengths of the JSON content
355-
356- Format your response as:
357- SCORE: [number between 0.0 and 1.0]
358- EXPLANATION: [your detailed explanation]
359- """
360- try :
361- import os
362-
363- api_key = openai_api_key or os .environ .get ("OPENAI_API_KEY" )
364- if not api_key :
365- raise ValueError ("OpenAI API key not provided" )
366- client = OpenAI (api_key = api_key )
367- response = client .chat .completions .create (
368- model = model ,
369- temperature = temperature ,
370- messages = [{"role" : "user" , "content" : prompt }],
371- )
372- llm_response = response .choices [0 ].message .content or ""
373- score_match = re .search (r"SCORE:\s*([\d.]+)" , llm_response )
374- explanation_match = re .search (r"EXPLANATION:\s*(.*)" , llm_response , re .DOTALL )
375- if score_match :
376- try :
377- llm_score = float (score_match .group (1 ))
378- llm_score = max (0.0 , min (llm_score , 1.0 ))
379- except ValueError :
380- llm_score = 0.5
381- else :
382- llm_score = 0.5
383- llm_reason = explanation_match .group (1 ).strip () if explanation_match else "No explanation provided"
384- except Exception as e :
385- llm_score = 0.0
386- llm_reason = f"Error calling OpenAI API: { str (e )} "
387-
388- combined_metrics = {}
389- for key , metric_val in schema_result .metrics .items ():
390- if key != "schema_similarity" :
391- combined_metrics [f"schema_{ key } " ] = metric_val
392- else :
393- combined_metrics [key ] = metric_val
394315
395- combined_metrics ["llm_judge" ] = MetricResult (
396- score = llm_score ,
397- reason = llm_reason ,
398- is_score_valid = llm_score >= 0.8 ,
399- )
400- combined_metrics ["schema_score" ] = MetricResult (
401- score = schema_result .score ,
402- reason = f"Schema validation score: { schema_result .score :.2f} " ,
403- is_score_valid = schema_result .score == 1.0 ,
404- )
405- combined_metrics ["llm_score" ] = MetricResult (
406- score = llm_score ,
407- reason = f"LLM judge score: { llm_score :.2f} " ,
408- is_score_valid = llm_score >= 0.8 ,
409- )
410-
411- schema_weight = normalized_weights .get ("schema" , 0.7 )
412- llm_weight = normalized_weights .get ("llm" , 0.3 )
413- final_score = (schema_result .score * schema_weight ) + (llm_score * llm_weight )
414- final_reason = f"Composite score. Schema ({ schema_result .score :.2f} * { schema_weight :.2f} ) + LLM ({ llm_score :.2f} * { llm_weight :.2f} )."
316+ if json_str_from_msg :
317+ json_content = json_str_from_msg
415318
416- combined_metrics ["weights" ] = MetricResult (
417- score = 0.0 ,
418- reason = f"Weights used - Schema: { schema_weight :.2f} , LLM: { llm_weight :.2f} " ,
419- is_score_valid = True ,
420- )
319+ # Now delegate to the combined schema+LLM judge function
320+ combined_result = json_schema_reward_with_llm_judge (
321+ messages = messages ,
322+ ground_truth = ground_truth ,
323+ json_content = json_content ,
324+ expected_schema = expected_schema ,
325+ expected_behavior = expected_behavior ,
326+ ** kwargs ,
327+ )
328+ return combined_result
421329
422- return EvaluateResult (score = final_score , reason = final_reason , metrics = combined_metrics )
330+ # If no expected_behavior provided, return the schema-only result
331+ return schema_result
0 commit comments