11from __future__ import annotations
22
33import os
4- from typing import Any , Dict , List , Optional
4+ from typing import List
55
66from langchain_core .messages import AIMessage , BaseMessage , HumanMessage , SystemMessage , ToolMessage
7+ from eval_protocol .human_id import generate_id
8+ import json
79
810from eval_protocol .models import Message
911
@@ -14,10 +16,8 @@ def _dbg_enabled() -> bool:
1416
1517def _dbg_print (* args ):
1618 if _dbg_enabled ():
17- try :
18- print (* args )
19- except Exception :
20- pass
19+ # Best-effort debug print without broad exception handling
20+ print (* args )
2121
2222
2323def serialize_lc_message_to_ep (msg : BaseMessage ) -> Message :
@@ -36,25 +36,126 @@ def serialize_lc_message_to_ep(msg: BaseMessage) -> Message:
3636 return ep_msg
3737
3838 if isinstance (msg , AIMessage ):
39- content = ""
39+ # Extract visible content and hidden reasoning content if present
40+ content_text = ""
41+ reasoning_texts : List [str ] = []
42+
4043 if isinstance (msg .content , str ):
41- content = msg .content
44+ content_text = msg .content
4245 elif isinstance (msg .content , list ):
43- parts : List [str ] = []
46+ text_parts : List [str ] = []
4447 for item in msg .content :
4548 if isinstance (item , dict ):
46- if item .get ("type" ) == "text" :
47- parts .append (str (item .get ("text" , "" )))
49+ item_type = item .get ("type" )
50+ if item_type == "text" :
51+ text_parts .append (str (item .get ("text" , "" )))
52+ elif item_type in ("reasoning" , "thinking" , "thought" ):
53+ # Some providers return dedicated reasoning parts
54+ maybe_text = item .get ("text" ) or item .get ("content" )
55+ if isinstance (maybe_text , str ):
56+ reasoning_texts .append (maybe_text )
4857 elif isinstance (item , str ):
49- parts .append (item )
50- content = "\n " .join (parts )
58+ text_parts .append (item )
59+ content_text = "\n " .join ([t for t in text_parts if t ])
60+
61+ # Additional place providers may attach reasoning
62+ additional_kwargs = getattr (msg , "additional_kwargs" , None )
63+ if isinstance (additional_kwargs , dict ):
64+ rk = additional_kwargs .get ("reasoning_content" )
65+ if isinstance (rk , str ) and rk :
66+ reasoning_texts .append (rk )
67+
68+ # Fireworks and others sometimes nest under `reasoning` or `metadata`
69+ nested_reasoning = additional_kwargs .get ("reasoning" )
70+ if isinstance (nested_reasoning , dict ):
71+ inner = nested_reasoning .get ("content" ) or nested_reasoning .get ("text" )
72+ if isinstance (inner , str ) and inner :
73+ reasoning_texts .append (inner )
74+
75+ # Capture tool calls and function_call if present on AIMessage
76+ def _normalize_tool_calls (raw_tcs ):
77+ normalized = []
78+ for tc in raw_tcs or []:
79+ if isinstance (tc , dict ) and "function" in tc :
80+ # Assume already OpenAI style
81+ fn = tc .get ("function" , {})
82+ # Ensure arguments is a string
83+ args = fn .get ("arguments" )
84+ if not isinstance (args , str ):
85+ try :
86+ args = json .dumps (args )
87+ except Exception :
88+ args = str (args )
89+ normalized .append (
90+ {
91+ "id" : tc .get ("id" ) or generate_id (),
92+ "type" : tc .get ("type" ) or "function" ,
93+ "function" : {"name" : fn .get ("name" , "" ), "arguments" : args },
94+ }
95+ )
96+ elif isinstance (tc , dict ) and ("name" in tc ) and ("args" in tc or "arguments" in tc ):
97+ # LangChain tool schema → OpenAI function-call schema
98+ name = tc .get ("name" , "" )
99+ args_val = tc .get ("args" , tc .get ("arguments" , {}))
100+ if not isinstance (args_val , str ):
101+ try :
102+ args_val = json .dumps (args_val )
103+ except Exception :
104+ args_val = str (args_val )
105+ normalized .append (
106+ {
107+ "id" : tc .get ("id" ) or generate_id (),
108+ "type" : "function" ,
109+ "function" : {"name" : name , "arguments" : args_val },
110+ }
111+ )
112+ else :
113+ # Best-effort: stringify unknown formats
114+ normalized .append (
115+ {
116+ "id" : generate_id (),
117+ "type" : "function" ,
118+ "function" : {
119+ "name" : str (tc .get ("name" , "tool" )) if isinstance (tc , dict ) else "tool" ,
120+ "arguments" : json .dumps (tc ) if not isinstance (tc , str ) else tc ,
121+ },
122+ }
123+ )
124+ return normalized if normalized else None
125+
126+ extracted_tool_calls = None
127+ tc_attr = getattr (msg , "tool_calls" , None )
128+ if isinstance (tc_attr , list ):
129+ extracted_tool_calls = _normalize_tool_calls (tc_attr )
130+
131+ if extracted_tool_calls is None and isinstance (additional_kwargs , dict ):
132+ maybe_tc = additional_kwargs .get ("tool_calls" )
133+ if isinstance (maybe_tc , list ):
134+ extracted_tool_calls = _normalize_tool_calls (maybe_tc )
135+
136+ extracted_function_call = None
137+ fc_attr = getattr (msg , "function_call" , None )
138+ if fc_attr :
139+ extracted_function_call = fc_attr
140+ if extracted_function_call is None and isinstance (additional_kwargs , dict ):
141+ maybe_fc = additional_kwargs .get ("function_call" )
142+ if maybe_fc :
143+ extracted_function_call = maybe_fc
51144
52- ep_msg = Message (role = "assistant" , content = content )
145+ ep_msg = Message (
146+ role = "assistant" ,
147+ content = content_text ,
148+ reasoning_content = ("\n " .join (reasoning_texts ) if reasoning_texts else None ),
149+ tool_calls = extracted_tool_calls , # type: ignore[arg-type]
150+ function_call = extracted_function_call , # type: ignore[arg-type]
151+ )
53152 _dbg_print (
54153 "[EP-Ser] -> EP Message:" ,
55154 {
56155 "role" : ep_msg .role ,
57156 "content_len" : len (ep_msg .content or "" ),
157+ "has_reasoning" : bool (ep_msg .reasoning_content ),
158+ "has_tool_calls" : bool (ep_msg .tool_calls ),
58159 },
59160 )
60161 return ep_msg
@@ -107,8 +208,6 @@ def serialize_ep_messages_to_lc(messages: List[Message]) -> List[BaseMessage]:
107208 elif role == "assistant" :
108209 lc_messages .append (AIMessage (content = text ))
109210 elif role == "system" :
110- from langchain_core .messages import SystemMessage # local import to avoid unused import
111-
112211 lc_messages .append (SystemMessage (content = text ))
113212 else :
114213 lc_messages .append (HumanMessage (content = text ))
0 commit comments