Skip to content

Commit 8148772

Browse files
author
Dylan Huang
committed
Merge branch 'main' into derekx/quick-start
2 parents da4023d + 8101180 commit 8148772

17 files changed

+1159
-202
lines changed

eval_protocol/adapters/bigquery.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from __future__ import annotations
88

99
import logging
10-
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Union, cast, TypeAlias
10+
from typing import Any, Callable, Dict, Iterator, List, Optional, TypeAlias
1111

1212
from eval_protocol.models import CompletionParams, EvaluationRow, InputMetadata, Message
1313

@@ -108,10 +108,7 @@ def __init__(
108108
# Avoid strict typing on optional dependency
109109
self.client = _bigquery_runtime.Client(**client_args) # type: ignore[no-untyped-call, assignment]
110110

111-
except DefaultCredentialsError as e:
112-
logger.error("Failed to authenticate with BigQuery: %s", e)
113-
raise
114-
except Exception as e:
111+
except (DefaultCredentialsError, ImportError, ValueError, TypeError) as e:
115112
logger.error("Failed to initialize BigQuery client: %s", e)
116113
raise
117114

@@ -191,10 +188,7 @@ def get_evaluation_rows(
191188

192189
row_count += 1
193190

194-
except (NotFound, Forbidden) as e:
195-
logger.error("BigQuery access error: %s", e)
196-
raise
197-
except Exception as e:
191+
except (NotFound, Forbidden, RuntimeError, ValueError, TypeError, AttributeError) as e:
198192
logger.error("Error executing BigQuery query: %s", e)
199193
raise
200194

Lines changed: 142 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
22

33
import os
4-
from typing import Any, Dict, List, Optional
4+
from typing import List
55

6-
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, ToolMessage
6+
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage, ToolMessage
7+
from eval_protocol.human_id import generate_id
8+
import json
79

810
from eval_protocol.models import Message
911

@@ -14,10 +16,8 @@ def _dbg_enabled() -> bool:
1416

1517
def _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

2323
def serialize_lc_message_to_ep(msg: BaseMessage) -> Message:
@@ -36,88 +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)
51-
52-
tool_calls_payload: Optional[List[Dict[str, Any]]] = None
53-
54-
def _normalize_tool_calls(tc_list: List[Any]) -> List[Dict[str, Any]]:
55-
mapped: List[Dict[str, Any]] = []
56-
for call in tc_list:
57-
if not isinstance(call, dict):
58-
continue
59-
try:
60-
call_id = call.get("id") or "toolcall_0"
61-
if isinstance(call.get("function"), dict):
62-
fn = call["function"]
63-
fn_name = fn.get("name") or call.get("name") or "tool"
64-
fn_args = fn.get("arguments")
65-
else:
66-
fn_name = call.get("name") or "tool"
67-
fn_args = call.get("arguments") if call.get("arguments") is not None else call.get("args")
68-
if not isinstance(fn_args, str):
69-
import json as _json
70-
71-
fn_args = _json.dumps(fn_args or {}, ensure_ascii=False)
72-
mapped.append(
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(
73115
{
74-
"id": call_id,
116+
"id": generate_id(),
75117
"type": "function",
76-
"function": {"name": fn_name, "arguments": fn_args},
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+
},
77122
}
78123
)
79-
except Exception:
80-
continue
81-
return mapped
82-
83-
ak = getattr(msg, "additional_kwargs", None)
84-
if isinstance(ak, dict):
85-
tc = ak.get("tool_calls")
86-
if isinstance(tc, list) and tc:
87-
mapped = _normalize_tool_calls(tc)
88-
if mapped:
89-
tool_calls_payload = mapped
90-
91-
if tool_calls_payload is None:
92-
raw_attr_tc = getattr(msg, "tool_calls", None)
93-
if isinstance(raw_attr_tc, list) and raw_attr_tc:
94-
mapped = _normalize_tool_calls(raw_attr_tc)
95-
if mapped:
96-
tool_calls_payload = mapped
97-
98-
# Extract reasoning/thinking parts into reasoning_content
99-
reasoning_content = None
100-
if isinstance(msg.content, list):
101-
collected = [
102-
it.get("thinking", "") for it in msg.content if isinstance(it, dict) and it.get("type") == "thinking"
103-
]
104-
if collected:
105-
reasoning_content = "\n\n".join([s for s in collected if s]) or None
106-
107-
# Message.tool_calls expects List[ChatCompletionMessageToolCall] | None.
108-
# We pass through Dicts at runtime but avoid type error by casting.
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
144+
109145
ep_msg = Message(
110146
role="assistant",
111-
content=content,
112-
tool_calls=tool_calls_payload, # type: ignore[arg-type]
113-
reasoning_content=reasoning_content,
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]
114151
)
115152
_dbg_print(
116153
"[EP-Ser] -> EP Message:",
117154
{
118155
"role": ep_msg.role,
119156
"content_len": len(ep_msg.content or ""),
120-
"tool_calls": len(ep_msg.tool_calls or []) if isinstance(ep_msg.tool_calls, list) else 0,
157+
"has_reasoning": bool(ep_msg.reasoning_content),
158+
"has_tool_calls": bool(ep_msg.tool_calls),
121159
},
122160
)
123161
return ep_msg
@@ -141,3 +179,36 @@ def _normalize_tool_calls(tc_list: List[Any]) -> List[Dict[str, Any]]:
141179
ep_msg = Message(role=getattr(msg, "type", "assistant"), content=str(getattr(msg, "content", "")))
142180
_dbg_print("[EP-Ser] -> EP Message (fallback):", {"role": ep_msg.role, "len": len(ep_msg.content or "")})
143181
return ep_msg
182+
183+
184+
def serialize_ep_messages_to_lc(messages: List[Message]) -> List[BaseMessage]:
185+
"""Convert eval_protocol Message objects to LangChain BaseMessage list.
186+
187+
- Flattens content parts into strings when content is a list
188+
- Maps EP roles to LC message classes
189+
"""
190+
lc_messages: List[BaseMessage] = []
191+
for m in messages or []:
192+
content = m.content
193+
if isinstance(content, list):
194+
text_parts: List[str] = []
195+
for part in content:
196+
try:
197+
text_parts.append(getattr(part, "text", ""))
198+
except AttributeError:
199+
pass
200+
content = "\n".join([t for t in text_parts if t])
201+
if content is None:
202+
content = ""
203+
text = str(content)
204+
205+
role = (m.role or "").lower()
206+
if role == "user":
207+
lc_messages.append(HumanMessage(content=text))
208+
elif role == "assistant":
209+
lc_messages.append(AIMessage(content=text))
210+
elif role == "system":
211+
lc_messages.append(SystemMessage(content=text))
212+
else:
213+
lc_messages.append(HumanMessage(content=text))
214+
return lc_messages

0 commit comments

Comments
 (0)