Skip to content

Commit 2a87a07

Browse files
authored
Merge pull request #13 from wippyai/hotfix/openai-mapper
- fixed dangling function calls for GPT models
2 parents fa44d5d + 604b44a commit 2a87a07

File tree

1 file changed

+55
-21
lines changed

1 file changed

+55
-21
lines changed

src/llm/src/openai/mapper.lua

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ function openai_mapper.map_messages(contract_messages, options)
142142

143143
while i <= #contract_messages do
144144
local msg = contract_messages[i]
145-
146145
-- Clear metadata
147146
msg.metadata = nil
148147

@@ -176,26 +175,53 @@ function openai_mapper.map_messages(contract_messages, options)
176175

177176
i = i + 1 -- Move to next message
178177

179-
-- Check if next messages are function_calls that should be consolidated
180-
while i <= #contract_messages and contract_messages[i].role == "function_call" do
181-
local func_msg = contract_messages[i]
182-
183-
if func_msg.function_call and func_msg.function_call.id then
184-
local arguments = func_msg.function_call.arguments
185-
if type(arguments) == "table" and not next(arguments) then
186-
arguments = { invoke = true }
187-
end
188-
189-
table.insert(assistant_msg.tool_calls, {
190-
id = func_msg.function_call.id,
191-
type = "function",
192-
["function"] = {
193-
name = func_msg.function_call.name,
194-
arguments = (type(arguments) == "table") and json.encode(arguments) or tostring(arguments)
195-
}
178+
-- Collect ALL function_calls that belong to this assistant message
179+
-- Look ahead through remaining messages to find all related function calls
180+
local function_calls_found = {}
181+
local j = i
182+
while j <= #contract_messages do
183+
local current_msg = contract_messages[j]
184+
185+
if current_msg.role == "function_call" and current_msg.function_call and current_msg.function_call.id then
186+
-- Collect this function call
187+
table.insert(function_calls_found, {
188+
index = j,
189+
msg = current_msg
196190
})
191+
elseif current_msg.role == "assistant" then
192+
-- Stop when we hit another assistant message
193+
break
194+
elseif current_msg.role == "function_result" then
195+
-- This handles interleaved function_call/function_result patterns
196+
elseif current_msg.role == "user" then
197+
-- Stop when we hit a user message (new conversation turn)
198+
break
197199
end
198-
i = i + 1
200+
j = j + 1
201+
end
202+
203+
-- Process all collected function calls
204+
for _, func_call_info in ipairs(function_calls_found) do
205+
local func_msg = func_call_info.msg
206+
local arguments = func_msg.function_call.arguments
207+
if type(arguments) == "table" and not next(arguments) then
208+
arguments = { invoke = true }
209+
end
210+
211+
table.insert(assistant_msg.tool_calls, {
212+
id = func_msg.function_call.id,
213+
type = "function",
214+
["function"] = {
215+
name = func_msg.function_call.name,
216+
arguments = (type(arguments) == "table") and json.encode(arguments) or tostring(arguments)
217+
}
218+
})
219+
end
220+
221+
-- Mark all collected function calls as processed by creating a set of indices
222+
local processed_indices = {}
223+
for _, func_call_info in ipairs(function_calls_found) do
224+
processed_indices[func_call_info.index] = true
199225
end
200226

201227
-- Remove tool_calls field if empty
@@ -204,6 +230,15 @@ function openai_mapper.map_messages(contract_messages, options)
204230
end
205231

206232
table.insert(processed_messages, assistant_msg)
233+
234+
-- Continue processing, but skip the function calls we already processed
235+
while i <= #contract_messages do
236+
if processed_indices[i] then
237+
i = i + 1
238+
else
239+
break
240+
end
241+
end
207242
elseif msg.role == "function_result" then
208243
-- Convert function results to tool messages - use simple string content
209244
local tool_content = ""
@@ -257,14 +292,13 @@ function openai_mapper.map_messages(contract_messages, options)
257292
})
258293
i = i + 1
259294
elseif msg.role == "function_call" then
260-
-- Standalone function_call (shouldn't happen with proper consolidation above)
295+
-- Skip orphaned function calls - they should have been collected by assistant message processing
261296
i = i + 1
262297
else
263298
-- Skip unknown message types
264299
i = i + 1
265300
end
266301
end
267-
268302
return processed_messages
269303
end
270304

0 commit comments

Comments
 (0)