This commit is contained in:
Tim Baek
2026-02-09 08:07:33 +04:00
parent 26460917c4
commit aa8c2959ca
2 changed files with 482 additions and 560 deletions
File diff suppressed because it is too large Load Diff
+79 -33
View File
@@ -128,22 +128,40 @@ def get_content_from_message(message: dict) -> Optional[str]:
return None
def convert_output_to_messages(output: list) -> list[dict]:
def convert_output_to_messages(output: list, raw: bool = False) -> list[dict]:
"""
Convert OR-aligned output items to OpenAI-format messages for LLM consumption.
This is the inverse of convert_content_blocks_to_output() in middleware.py.
Convert OR-aligned output items to OpenAI Chat Completion-format messages.
This reconstructs the full conversation from the stored Responses API-native
output items, including assistant messages with tool_calls arrays and tool
role messages.
Args:
output: List of OR-aligned output items (Responses API format).
raw: If True, include reasoning blocks (with original tags) and code
interpreter blocks for LLM re-processing follow-ups.
"""
if not output or not isinstance(output, list):
return []
messages = []
pending_tool_calls = []
pending_content = []
def flush_pending():
nonlocal pending_content, pending_tool_calls
if pending_content or pending_tool_calls:
messages.append({
"role": "assistant",
"content": "\n".join(pending_content) if pending_content else "",
**({"tool_calls": pending_tool_calls} if pending_tool_calls else {}),
})
pending_content = []
pending_tool_calls = []
for item in output:
item_type = item.get("type", "")
if item_type == "message":
# Extract text from output_text content parts
content_parts = item.get("content", [])
@@ -153,58 +171,86 @@ def convert_output_to_messages(output: list) -> list[dict]:
text += part.get("text", "")
if text:
pending_content.append(text)
elif item_type == "function_call":
# Collect tool calls to batch into assistant message
arguments = item.get("arguments", "{}")
# Ensure arguments is always a JSON string
if not isinstance(arguments, str):
arguments = json.dumps(arguments)
pending_tool_calls.append({
"id": item.get("call_id", ""),
"type": "function",
"function": {
"name": item.get("name", ""),
"arguments": item.get("arguments", "{}"),
"arguments": arguments,
}
})
elif item_type == "function_call_output":
# Flush any pending content/tool_calls before adding tool result
if pending_content or pending_tool_calls:
messages.append({
"role": "assistant",
"content": "\n".join(pending_content) if pending_content else "",
**({"tool_calls": pending_tool_calls} if pending_tool_calls else {}),
})
pending_content = []
pending_tool_calls = []
flush_pending()
# Extract text from output content parts
output_parts = item.get("output", [])
content = ""
for part in output_parts:
if part.get("type") == "input_text":
content += part.get("text", "")
messages.append({
"role": "tool",
"tool_call_id": item.get("call_id", ""),
"content": content,
})
elif item_type == "reasoning":
# Skip reasoning blocks for LLM messages
pass
if raw:
# Include reasoning with original tags for LLM re-processing
reasoning_text = ""
source_list = item.get("summary", []) or item.get("content", [])
for part in source_list:
if part.get("type") == "output_text":
reasoning_text += part.get("text", "")
elif "text" in part:
reasoning_text += part.get("text", "")
if reasoning_text:
start_tag = item.get("start_tag", "<think>")
end_tag = item.get("end_tag", "</think>")
pending_content.append(
f"{start_tag}{reasoning_text}{end_tag}"
)
# else: skip reasoning blocks for normal LLM messages
elif item_type == "open_webui:code_interpreter":
if raw:
# Include code interpreter content for LLM re-processing
code = item.get("code", "")
code_output = item.get("output", "")
if code:
lang = item.get("lang", "python")
pending_content.append(f"```{lang}\n{code}\n```")
if code_output:
if isinstance(code_output, dict):
stdout = code_output.get("stdout", "")
result = code_output.get("result", "")
output_text = stdout or result
else:
output_text = str(code_output)
if output_text:
pending_content.append(f"Output:\n{output_text}")
# else: skip extension types
elif item_type.startswith("open_webui:"):
# Skip extension types
# Skip other extension types
pass
# Flush remaining content/tool_calls
if pending_content or pending_tool_calls:
messages.append({
"role": "assistant",
"content": "\n".join(pending_content) if pending_content else "",
**({"tool_calls": pending_tool_calls} if pending_tool_calls else {}),
})
flush_pending()
return messages