This commit is contained in:
Timothy Jaeryang Baek 2026-10-29 17:20:55 +09:00
parent 193cf225f3
commit df61370456
2 changed files with 27 additions and 32 deletions

View file

@ -386,17 +386,6 @@ def convert_output_to_messages(
pending_reasoning_details = []
pending_tool_image_urls = []
pending_tool_outputs = []
completed_call_ids = {
item.get('call_id')
for item in output
if item.get('type') == 'function_call'
and item.get('call_id')
and item.get('status') in {'completed', 'failed', 'rejected'}
}
result_call_ids = {
item.get('call_id') for item in output if item.get('type') == 'function_call_output' and item.get('call_id')
}
function_call_ids = completed_call_ids & result_call_ids
def flush_pending():
nonlocal pending_content, pending_tool_calls, pending_reasoning, pending_reasoning_details
@ -448,6 +437,11 @@ def convert_output_to_messages(
if not pending_tool_outputs:
return
pending_tool_outputs = [
result
for result in pending_tool_outputs
if any(call['id'] == result.get('call_id') for call in pending_tool_calls)
]
flush_pending()
for output_item in pending_tool_outputs:
output_parts = output_item.get('output', [])
@ -510,7 +504,7 @@ def convert_output_to_messages(
pending_content.append(text)
elif item_type == 'function_call':
if item.get('call_id') not in function_call_ids:
if not item.get('call_id') or item.get('status') not in {'completed', 'failed', 'rejected'}:
continue
# Collect tool calls to batch into assistant message
@ -523,9 +517,6 @@ def convert_output_to_messages(
)
elif item_type == 'function_call_output':
if item.get('call_id') not in function_call_ids:
continue
pending_tool_outputs.append(item)
elif item_type == 'reasoning':

View file

@ -32,34 +32,38 @@ def normalize_tool_output(output):
if not isinstance(output, list):
return output
items = [dict(item) for item in output if isinstance(item, dict)]
deduped = []
positions = {}
normalized = []
indices = {}
start = 0
for item in items:
kind = item.get('type')
item_type = item.get('type')
# A new assistant item after tool results starts a new round.
if normalized and normalized[-1].get('type') == 'function_call_output' and item_type != 'function_call_output':
pair_results(normalized[start:])
indices.clear()
start = len(normalized)
call_id = item.get('call_id') or item.get('id')
if kind in {'function_call', 'function_call_output'} and call_id:
key = (kind, call_id)
if key in positions:
deduped[positions[key]] = {**deduped[positions[key]], **item}
if item_type in {'function_call', 'function_call_output'} and call_id:
key = (item_type, call_id)
if key in indices:
normalized[indices[key]] = {**normalized[indices[key]], **item}
continue
positions[key] = len(deduped)
deduped.append(item)
return pair_results(deduped)
indices[key] = len(normalized)
normalized.append(item)
pair_results(normalized[start:])
return normalized
def pair_results(deduped):
results = {item.get('call_id'): item for item in deduped if item.get('type') == 'function_call_output'}
for item in deduped:
def pair_results(output):
results = {item.get('call_id'): item for item in output if item.get('type') == 'function_call_output'}
for item in output:
if item.get('type') != 'function_call':
continue
result = results.get(item.get('call_id'))
if result:
if result.get('status'):
item['status'] = result['status']
for key in ('started_at', 'ended_at', 'duration', 'error_type'):
if key in result and key not in item:
item[key] = result[key]
elif item.get('status') == 'completed':
item['status'] = 'unknown'
return deduped
return output