Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/10_Agentic_Inference/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ contiguous and ordered by increasing `turn`.

Required fields are `conversation_id`, `turn`, and `role`. User rows normally
include `content`; agentic rows can also include `system`, `tools`,
`tool_calls`, `tool_results`, `reasoning_content`, and `delay_seconds`.
`tool_calls`, `tool_results`, `reasoning_content`, `reasoning`, and
`delay_seconds`.

Place the dataset under `examples/10_Agentic_Inference/datasets/` or point the YAML at
another accessible JSONL path.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ def _build_conversation_metadata(

for _, row in sorted_group.iterrows():
role = row.get("role")
reasoning_content = row.get("reasoning_content")
reasoning = row.get("reasoning")
if (
reasoning_content is not None
and not (
isinstance(reasoning_content, float) and pd.isna(reasoning_content)
)
and reasoning is not None
and not (isinstance(reasoning, float) and pd.isna(reasoning))
):
raise InputValidationError(
f"conversation {row.get('conversation_id')!r} turn {row.get('turn')} "
"has both 'reasoning_content' and 'reasoning' values"
)
Comment on lines 148 to +163

# Format this row into message(s) using the same field extraction as before.
expanded = _expand_tool_results(row)
Expand All @@ -161,6 +175,7 @@ def _build_conversation_metadata(
"tool_calls",
"tool_results",
"reasoning_content",
"reasoning",
):
val = row.get(key)
if val is not None and not (isinstance(val, float) and pd.isna(val)):
Expand Down
4 changes: 3 additions & 1 deletion src/inference_endpoint/openai/openai_msgspec_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@

def _chat_message_from_dict(msg: dict) -> "ChatMessage":
"""Build a ChatMessage from a dict, forwarding all supported fields."""
reasoning_value = msg.get("reasoning_content") or msg.get("reasoning")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the or operator here can lead to unexpected behavior if reasoning_content is an empty string "" (which is a falsy value in Python). If reasoning_content is "" and reasoning is None or not present, reasoning_value will incorrectly evaluate to None instead of preserving the empty string.\n\nTo make this more robust, explicitly check for None using a conditional expression.

Suggested change
reasoning_value = msg.get("reasoning_content") or msg.get("reasoning")
reasoning_value = msg.get("reasoning_content") if msg.get("reasoning_content") is not None else msg.get("reasoning")

return ChatMessage(
Comment on lines 50 to 53
role=msg["role"],
content=msg.get("content"),
name=msg.get("name"),
tool_calls=msg.get("tool_calls"),
tool_call_id=msg.get("tool_call_id"),
reasoning_content=msg.get("reasoning_content"),
reasoning_content=reasoning_value,
reasoning=reasoning_value,
)


Expand Down
1 change: 1 addition & 0 deletions src/inference_endpoint/openai/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class ChatMessage(
tool_calls: list[dict[str, Any]] | None = None
tool_call_id: str | None = None
reasoning_content: str | None = None
reasoning: str | None = None


# gc=False: audit 2026-05: request containers set at construction; frozen=True blocks field reassignment.
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/dataset_manager/test_agentic_inference_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,51 @@ def test_build_metadata_pre_built_messages_no_tools():
assert msgs[2] == {"role": "user", "content": "C"}


@pytest.mark.unit
@pytest.mark.parametrize(
("message_fields", "expected_field"),
[
({"reasoning_content": "thinking"}, "reasoning_content"),
({"reasoning": "thinking"}, "reasoning"),
({"reasoning_content": "thinking", "reasoning": "thinking"}, None),
],
ids=["reasoning_content", "reasoning", "both_rejected"],
)
def test_pre_built_messages_reasoning_alias_policy(
message_fields: dict[str, str], expected_field: str | None
):
df = pd.DataFrame(
[
{"conversation_id": "c1", "turn": 1, "role": "user", "content": "A"},
{
"conversation_id": "c1",
"turn": 2,
"role": "assistant",
"content": "B",
**message_fields,
},
{"conversation_id": "c1", "turn": 3, "role": "user", "content": "C"},
]
)
ds = AgenticInferenceDataset(df)

if expected_field is None:
with pytest.raises(
InputValidationError,
match=(
"conversation 'c1' turn 2 has both "
"'reasoning_content' and 'reasoning'"
),
):
ds.load()
return

ds.load()
assert ds.conversation_metadata is not None
msgs = ds.conversation_metadata.pre_built_messages_by_key[("c1", 3)]
assert msgs[1][expected_field] == "thinking"


@pytest.mark.unit
def test_load_sample_includes_messages():
"""load_sample returns messages with the complete message list."""
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/openai/test_msgspec_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,39 @@ def test_chat_message_from_dict_all_fields():
assert msg.tool_call_id is None


@pytest.mark.unit
@pytest.mark.parametrize(
("message_fields", "expected"),
[
({"reasoning_content": "thinking via sglang"}, "thinking via sglang"),
({"reasoning": "thinking via vllm"}, "thinking via vllm"),
({"reasoning_content": "sglang", "reasoning": "vllm"}, "sglang"),
],
ids=["reasoning_content", "reasoning", "reasoning_content_precedence"],
Comment on lines +171 to +176
)
def test_to_endpoint_request_forwards_both_reasoning_aliases(message_fields, expected):
query = Query(
id="q-reasoning",
data={
"model": "m",
"messages": [
{
"role": "assistant",
"content": "answer",
**message_fields,
}
],
},
)

request = OpenAIMsgspecAdapter.to_endpoint_request(query)
payload = json.loads(msgspec.json.encode(request))

msg = payload["messages"][0]
assert msg["reasoning_content"] == expected
assert msg["reasoning"] == expected


Comment on lines +199 to +200
@pytest.mark.unit
def test_chat_message_content_optional():
"""ChatMessage accepts content=None for tool-dispatching assistant turns."""
Expand Down
Loading