Skip to content

Commit fd65b4f

Browse files
authored
Merge pull request #618 from trycua/feat/qwen-ollama-support-base
Allow for OpenAI Compatible Fallback Tool Output Parsing on Qwen
2 parents 4e26af0 + 833fc70 commit fd65b4f

File tree

1 file changed

+57
-4
lines changed
  • libs/python/agent/agent/loops

1 file changed

+57
-4
lines changed

libs/python/agent/agent/loops/qwen.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from ..responses import (
2121
convert_completion_messages_to_responses_items,
2222
convert_responses_items_to_completion_messages,
23+
make_reasoning_item,
2324
)
2425
from ..types import AgentCapability
2526

@@ -373,13 +374,23 @@ def _has_any_image(msgs: List[Dict[str, Any]]) -> bool:
373374
if _on_usage:
374375
await _on_usage(usage)
375376

376-
# Parse tool call from text; then convert to responses items via fake tool_calls
377+
# Extract response data
377378
resp_dict = response.model_dump() # type: ignore
378379
choice = (resp_dict.get("choices") or [{}])[0]
379-
content_text = ((choice.get("message") or {}).get("content")) or ""
380-
tool_call = _parse_tool_call_from_text(content_text)
380+
message = choice.get("message") or {}
381+
content_text = message.get("content") or ""
382+
tool_calls_array = message.get("tool_calls") or []
383+
reasoning_text = message.get("reasoning") or ""
381384

382385
output_items: List[Dict[str, Any]] = []
386+
387+
# Add reasoning if present (Ollama Cloud format)
388+
if reasoning_text:
389+
output_items.append(make_reasoning_item(reasoning_text))
390+
391+
# Priority 1: Try to parse tool call from content text (OpenRouter format)
392+
tool_call = _parse_tool_call_from_text(content_text)
393+
383394
if tool_call and isinstance(tool_call, dict):
384395
fn_name = tool_call.get("name") or "computer"
385396
raw_args = tool_call.get("arguments") or {}
@@ -405,8 +416,50 @@ def _has_any_image(msgs: List[Dict[str, Any]]) -> bool:
405416
],
406417
}
407418
output_items.extend(convert_completion_messages_to_responses_items([fake_cm]))
419+
elif tool_calls_array:
420+
# Priority 2: Use tool_calls field if present (Ollama Cloud format)
421+
# Process and unnormalize coordinates in tool calls
422+
processed_tool_calls = []
423+
for tc in tool_calls_array:
424+
function = tc.get("function", {})
425+
fn_name = function.get("name", "computer")
426+
args_str = function.get("arguments", "{}")
427+
428+
try:
429+
args = json.loads(args_str)
430+
431+
# Unnormalize coordinates if present
432+
if "coordinate" in args and last_rw is not None and last_rh is not None:
433+
args = await _unnormalize_coordinate(args, (last_rw, last_rh))
434+
435+
# Convert Qwen format to Computer Calls format if this is a computer tool
436+
if fn_name == "computer":
437+
converted_action = convert_qwen_tool_args_to_computer_action(args)
438+
if converted_action:
439+
args = converted_action
440+
441+
processed_tool_calls.append(
442+
{
443+
"type": tc.get("type", "function"),
444+
"id": tc.get("id", "call_0"),
445+
"function": {
446+
"name": fn_name,
447+
"arguments": json.dumps(args),
448+
},
449+
}
450+
)
451+
except json.JSONDecodeError:
452+
# Keep original if parsing fails
453+
processed_tool_calls.append(tc)
454+
455+
fake_cm = {
456+
"role": "assistant",
457+
"content": content_text if content_text else "",
458+
"tool_calls": processed_tool_calls,
459+
}
460+
output_items.extend(convert_completion_messages_to_responses_items([fake_cm]))
408461
else:
409-
# Fallback: just return assistant text
462+
# No tool calls found in either format, return text response
410463
fake_cm = {"role": "assistant", "content": content_text}
411464
output_items.extend(convert_completion_messages_to_responses_items([fake_cm]))
412465

0 commit comments

Comments
 (0)