9292 prepend_to_first_user_message_content ,
9393 convert_logit_bias_input_to_json ,
9494 get_content_from_message ,
95+ convert_output_to_messages ,
9596)
9697from open_webui .utils .tools import (
9798 get_tools ,
@@ -1399,6 +1400,30 @@ async def convert_url_images_to_base64(form_data):
13991400 return form_data
14001401
14011402
1403+ def process_messages_with_output (messages : list [dict ]) -> list [dict ]:
1404+ """
1405+ Process messages with OR-aligned output items for LLM consumption.
1406+
1407+ For assistant messages with 'output' field, produces properly formatted
1408+ OpenAI-style messages (tool_calls + tool results). Strips 'output' before LLM.
1409+ """
1410+ processed = []
1411+
1412+ for message in messages :
1413+ if message .get ("role" ) == "assistant" and message .get ("output" ):
1414+ # Use output items for clean OpenAI-format messages
1415+ output_messages = convert_output_to_messages (message ["output" ])
1416+ if output_messages :
1417+ processed .extend (output_messages )
1418+ continue
1419+
1420+ # Strip 'output' field before adding (LLM shouldn't see it)
1421+ clean_message = {k : v for k , v in message .items () if k != "output" }
1422+ processed .append (clean_message )
1423+
1424+ return processed
1425+
1426+
14021427async def process_chat_payload (request , form_data , user , metadata , model ):
14031428 # Pipeline Inlet -> Filter Inlet -> Chat Memory -> Chat Web Search -> Chat Image Generation
14041429 # -> Chat Code Interpreter (Form Data Update) -> (Default) Chat Tools Function Calling
@@ -1407,6 +1432,9 @@ async def process_chat_payload(request, form_data, user, metadata, model):
14071432 form_data = apply_params_to_form_data (form_data , model )
14081433 log .debug (f"form_data: { form_data } " )
14091434
1435+ # Process messages with OR-aligned output items for clean LLM messages
1436+ form_data ["messages" ] = process_messages_with_output (form_data .get ("messages" , []))
1437+
14101438 system_message = get_system_message (form_data .get ("messages" , []))
14111439 if system_message : # Chat Controls/User Settings
14121440 try :
@@ -2517,6 +2545,87 @@ def convert_content_blocks_to_messages(content_blocks, raw=False):
25172545
25182546 return messages
25192547
2548+ def convert_content_blocks_to_output (content_blocks ):
2549+ """
2550+ Convert content_blocks to Open Responses-aligned output items.
2551+ See: https://openresponses.org/specification
2552+ """
2553+ output_items = []
2554+
2555+ def next_id (prefix ):
2556+ return f"{ prefix } _{ uuid4 ().hex [:24 ]} "
2557+
2558+ for block in content_blocks :
2559+ block_type = block .get ("type" , "" )
2560+ # Use backend-provided ID if available, fallback to generated
2561+ block_id = block .get ("id" )
2562+
2563+ if block_type == "text" :
2564+ text_content = block .get ("content" , "" ).strip ()
2565+ if text_content :
2566+ output_items .append ({
2567+ "type" : "message" ,
2568+ "id" : block_id or next_id ("msg" ),
2569+ "status" : "completed" ,
2570+ "role" : "assistant" ,
2571+ "content" : [{"type" : "output_text" , "text" : text_content }],
2572+ })
2573+
2574+ elif block_type == "tool_calls" :
2575+ tool_calls = block .get ("content" , [])
2576+ results = block .get ("results" , [])
2577+
2578+ # Emit function_call items
2579+ for tool_call in tool_calls :
2580+ call_id = tool_call .get ("id" , "" )
2581+ func = tool_call .get ("function" , {})
2582+ output_items .append ({
2583+ "type" : "function_call" ,
2584+ "id" : call_id or next_id ("fc" ), # Use call_id as item id if available
2585+ "call_id" : call_id ,
2586+ "name" : func .get ("name" , "" ),
2587+ "arguments" : func .get ("arguments" , "{}" ),
2588+ "status" : "completed" if results else "in_progress" ,
2589+ })
2590+
2591+ # Emit function_call_output items
2592+ for result in results :
2593+ output_items .append ({
2594+ "type" : "function_call_output" ,
2595+ "id" : result .get ("id" ) or next_id ("fco" ),
2596+ "call_id" : result .get ("tool_call_id" , "" ),
2597+ "output" : [{"type" : "input_text" , "text" : result .get ("content" , "" )}],
2598+ "status" : "completed" ,
2599+ ** ({"files" : result .get ("files" )} if result .get ("files" ) else {}),
2600+ ** ({"embeds" : result .get ("embeds" )} if result .get ("embeds" ) else {}),
2601+ })
2602+
2603+ elif block_type == "reasoning" :
2604+ reasoning_content = block .get ("content" , "" ).strip ()
2605+ duration = block .get ("duration" )
2606+ output_items .append ({
2607+ "type" : "reasoning" ,
2608+ "id" : block_id or next_id ("r" ),
2609+ "status" : "completed" if duration is not None else "in_progress" ,
2610+ "content" : [{"type" : "output_text" , "text" : reasoning_content }] if reasoning_content else None ,
2611+ "summary" : None ,
2612+ })
2613+
2614+ elif block_type == "code_interpreter" :
2615+ code = block .get ("content" , "" )
2616+ output_val = block .get ("output" )
2617+ attrs = block .get ("attributes" , {})
2618+ output_items .append ({
2619+ "type" : "open_webui:code_interpreter" ,
2620+ "id" : block_id or next_id ("ci" ),
2621+ "status" : "completed" if output_val is not None else "in_progress" ,
2622+ "lang" : attrs .get ("lang" , "" ),
2623+ "code" : code ,
2624+ "output" : output_val ,
2625+ })
2626+
2627+ return output_items
2628+
25202629 def tag_content_handler (content_type , tags , content , content_blocks ):
25212630 end_flag = False
25222631
@@ -3132,6 +3241,9 @@ async def flush_pending_delta_data(threshold: int = 0):
31323241 "content" : serialize_content_blocks (
31333242 content_blocks
31343243 ),
3244+ "output" : convert_content_blocks_to_output (
3245+ content_blocks
3246+ ),
31353247 },
31363248 )
31373249 else :
@@ -3221,6 +3333,7 @@ async def flush_pending_delta_data(threshold: int = 0):
32213333 "type" : "chat:completion" ,
32223334 "data" : {
32233335 "content" : serialize_content_blocks (content_blocks ),
3336+ "output" : convert_content_blocks_to_output (content_blocks ),
32243337 },
32253338 }
32263339 )
@@ -3400,6 +3513,7 @@ async def flush_pending_delta_data(threshold: int = 0):
34003513 "type" : "chat:completion" ,
34013514 "data" : {
34023515 "content" : serialize_content_blocks (content_blocks ),
3516+ "output" : convert_content_blocks_to_output (content_blocks ),
34033517 },
34043518 }
34053519 )
@@ -3446,6 +3560,7 @@ async def flush_pending_delta_data(threshold: int = 0):
34463560 "type" : "chat:completion" ,
34473561 "data" : {
34483562 "content" : serialize_content_blocks (content_blocks ),
3563+ "output" : convert_content_blocks_to_output (content_blocks ),
34493564 },
34503565 }
34513566 )
@@ -3577,6 +3692,7 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0):
35773692 "type" : "chat:completion" ,
35783693 "data" : {
35793694 "content" : serialize_content_blocks (content_blocks ),
3695+ "output" : convert_content_blocks_to_output (content_blocks ),
35803696 },
35813697 }
35823698 )
@@ -3616,6 +3732,7 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0):
36163732 data = {
36173733 "done" : True ,
36183734 "content" : serialize_content_blocks (content_blocks ),
3735+ "output" : convert_content_blocks_to_output (content_blocks ),
36193736 "title" : title ,
36203737 }
36213738
@@ -3626,6 +3743,7 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0):
36263743 metadata ["message_id" ],
36273744 {
36283745 "content" : serialize_content_blocks (content_blocks ),
3746+ "output" : convert_content_blocks_to_output (content_blocks ),
36293747 },
36303748 )
36313749
@@ -3664,6 +3782,7 @@ def restricted_import(name, globals=None, locals=None, fromlist=(), level=0):
36643782 metadata ["message_id" ],
36653783 {
36663784 "content" : serialize_content_blocks (content_blocks ),
3785+ "output" : convert_content_blocks_to_output (content_blocks ),
36673786 },
36683787 )
36693788
0 commit comments