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
31 changes: 22 additions & 9 deletions openhands-sdk/openhands/sdk/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ def step(
# LLMResponse already contains the converted message and metrics snapshot
message: Message = llm_response.message

# Check if this is a reasoning-only response (e.g., from reasoning models)
# or a message-only response without tool calls
has_reasoning = (
message.responses_reasoning_item is not None
or message.reasoning_content is not None
or (message.thinking_blocks and len(message.thinking_blocks) > 0)
)
has_content = any(
isinstance(c, TextContent) and c.text.strip() for c in message.content
)

if message.tool_calls and len(message.tool_calls) > 0:
if not all(isinstance(c, TextContent) for c in message.content):
logger.warning(
Expand Down Expand Up @@ -254,16 +265,18 @@ def step(

if action_events:
self._execute_actions(conversation, action_events, on_event)
return
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we return here, we seem to miss the VLLM code below, the return_token_ids, is that intended?


else:
logger.debug("LLM produced a message response - awaits user input")
state.execution_status = ConversationExecutionStatus.FINISHED
msg_event = MessageEvent(
source="agent",
llm_message=message,
llm_response_id=llm_response.id,
)
on_event(msg_event)
# No tool calls - emit message event for reasoning or content responses
if not has_reasoning and not has_content:
logger.warning("LLM produced empty response - continuing agent loop")

msg_event = MessageEvent(
source="agent",
llm_message=message,
llm_response_id=llm_response.id,
)
on_event(msg_event)

# If using VLLM, we can get the raw prompt and response tokens
# that can be useful for RL training.
Expand Down
22 changes: 14 additions & 8 deletions openhands-sdk/openhands/sdk/llm/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,15 +458,10 @@ def to_responses_dict(self, *, vision_enabled: bool) -> list[dict[str, Any]]:
for c in self.content:
if isinstance(c, TextContent) and c.text:
content_items.append({"type": "output_text", "text": c.text})
if content_items:
items.append(
{
"type": "message",
"role": "assistant",
"content": content_items,
}
)

# Include prior turn's reasoning item exactly as received (if any)
# Note: Reasoning item must come BEFORE message/tool_calls for proper
# ordering
if self.responses_reasoning_item is not None:
ri = self.responses_reasoning_item
# Only send back if we have an id; required by the param schema
Expand All @@ -491,6 +486,17 @@ def to_responses_dict(self, *, vision_enabled: bool) -> list[dict[str, Any]]:
if ri.status:
reasoning_item["status"] = ri.status
items.append(reasoning_item)

# Add message item after reasoning (if content exists)
if content_items:
items.append(
{
"type": "message",
"role": "assistant",
"content": content_items,
}
)

# Emit assistant tool calls so subsequent function_call_output
# can match call_id
if self.tool_calls:
Expand Down
Loading