Skip to content

Commit 1da0fba

Browse files
authored
fix: responses only tools index (#622)
* fix: responses only tools index * fix: empty field
1 parent 734c965 commit 1da0fba

4 files changed

Lines changed: 264 additions & 82 deletions

File tree

core/relay/adaptor/openai/chat.go

Lines changed: 117 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,73 @@ import (
2323

2424
// chatCompletionStreamState manages state for ChatCompletion stream conversion
2525
type chatCompletionStreamState struct {
26-
messageID string
27-
meta *meta.Meta
28-
c *gin.Context
29-
currentToolCall *relaymodel.ToolCall
30-
currentToolCallID string
31-
toolCallArgs string
32-
hasToolCall bool
26+
messageID string
27+
created int64
28+
meta *meta.Meta
29+
c *gin.Context
30+
toolCallIndexByItemID map[string]int
31+
toolCallIndexByOutputIndex map[int]int
32+
nextToolCallIndex int
33+
hasToolCall bool
34+
}
35+
36+
func (s *chatCompletionStreamState) createdAt() int64 {
37+
if s.created == 0 {
38+
s.created = time.Now().Unix()
39+
}
40+
41+
return s.created
42+
}
43+
44+
func (s *chatCompletionStreamState) registerToolCall(
45+
event *relaymodel.ResponseStreamEvent,
46+
) int {
47+
if event.Item.ID != "" {
48+
if index, ok := s.toolCallIndexByItemID[event.Item.ID]; ok {
49+
return index
50+
}
51+
}
52+
53+
if event.OutputIndex != nil {
54+
if index, ok := s.toolCallIndexByOutputIndex[*event.OutputIndex]; ok {
55+
return index
56+
}
57+
}
58+
59+
index := s.nextToolCallIndex
60+
s.nextToolCallIndex++
61+
62+
if event.Item.ID != "" {
63+
s.toolCallIndexByItemID[event.Item.ID] = index
64+
}
65+
66+
if event.OutputIndex != nil {
67+
s.toolCallIndexByOutputIndex[*event.OutputIndex] = index
68+
}
69+
70+
return index
71+
}
72+
73+
func (s *chatCompletionStreamState) toolCallIndex(
74+
event *relaymodel.ResponseStreamEvent,
75+
) (int, bool) {
76+
if event.ItemID != "" {
77+
index, ok := s.toolCallIndexByItemID[event.ItemID]
78+
if ok {
79+
return index, true
80+
}
81+
}
82+
83+
if event.OutputIndex != nil {
84+
index, ok := s.toolCallIndexByOutputIndex[*event.OutputIndex]
85+
return index, ok
86+
}
87+
88+
if event.ItemID == "" && s.nextToolCallIndex > 0 {
89+
return s.nextToolCallIndex - 1, true
90+
}
91+
92+
return 0, false
3393
}
3494

3595
func responseModelName(meta *meta.Meta) string {
@@ -132,11 +192,14 @@ func (s *chatCompletionStreamState) handleResponseCreated(
132192
}
133193

134194
s.messageID = event.Response.ID
195+
if event.Response.CreatedAt != 0 {
196+
s.created = event.Response.CreatedAt
197+
}
135198

136199
return &relaymodel.ChatCompletionsStreamResponse{
137200
ID: s.messageID,
138201
Object: relaymodel.ChatCompletionChunkObject,
139-
Created: event.Response.CreatedAt,
202+
Created: s.createdAt(),
140203
Model: responseModelName(s.meta),
141204
Choices: []*relaymodel.ChatCompletionsStreamResponseChoice{
142205
{
@@ -160,7 +223,7 @@ func (s *chatCompletionStreamState) handleOutputTextDelta(
160223
return &relaymodel.ChatCompletionsStreamResponse{
161224
ID: s.messageID,
162225
Object: relaymodel.ChatCompletionChunkObject,
163-
Created: time.Now().Unix(),
226+
Created: s.createdAt(),
164227
Model: responseModelName(s.meta),
165228
Choices: []*relaymodel.ChatCompletionsStreamResponseChoice{
166229
{
@@ -183,7 +246,7 @@ func (s *chatCompletionStreamState) handleReasoningSummaryTextDelta(
183246
return &relaymodel.ChatCompletionsStreamResponse{
184247
ID: s.messageID,
185248
Object: relaymodel.ChatCompletionChunkObject,
186-
Created: time.Now().Unix(),
249+
Created: s.createdAt(),
187250
Model: responseModelName(s.meta),
188251
Choices: []*relaymodel.ChatCompletionsStreamResponseChoice{
189252
{
@@ -207,30 +270,21 @@ func (s *chatCompletionStreamState) handleOutputItemAdded(
207270
// Track function calls
208271
if event.Item.Type == relaymodel.InputItemTypeFunctionCall {
209272
s.hasToolCall = true
210-
s.currentToolCallID = event.Item.ID
211-
s.currentToolCall = &relaymodel.ToolCall{
212-
ID: event.Item.CallID,
213-
Type: relaymodel.ToolChoiceTypeFunction,
214-
Function: relaymodel.Function{
215-
Name: event.Item.Name,
216-
Arguments: "",
217-
},
218-
}
219-
s.toolCallArgs = ""
273+
toolCallIndex := s.registerToolCall(event)
220274

221275
// Send tool call start
222276
return &relaymodel.ChatCompletionsStreamResponse{
223277
ID: s.messageID,
224278
Object: relaymodel.ChatCompletionChunkObject,
225-
Created: time.Now().Unix(),
279+
Created: s.createdAt(),
226280
Model: responseModelName(s.meta),
227281
Choices: []*relaymodel.ChatCompletionsStreamResponseChoice{
228282
{
229283
Index: 0,
230284
Delta: relaymodel.Message{
231285
ToolCalls: []relaymodel.ToolCall{
232286
{
233-
Index: 0,
287+
Index: toolCallIndex,
234288
ID: event.Item.CallID,
235289
Type: relaymodel.ToolChoiceTypeFunction,
236290
Function: relaymodel.Function{
@@ -249,7 +303,7 @@ func (s *chatCompletionStreamState) handleOutputItemAdded(
249303
return &relaymodel.ChatCompletionsStreamResponse{
250304
ID: s.messageID,
251305
Object: relaymodel.ChatCompletionChunkObject,
252-
Created: time.Now().Unix(),
306+
Created: s.createdAt(),
253307
Model: responseModelName(s.meta),
254308
Choices: []*relaymodel.ChatCompletionsStreamResponseChoice{
255309
{
@@ -269,26 +323,28 @@ func (s *chatCompletionStreamState) handleOutputItemAdded(
269323
func (s *chatCompletionStreamState) handleFunctionCallArgumentsDelta(
270324
event *relaymodel.ResponseStreamEvent,
271325
) *relaymodel.ChatCompletionsStreamResponse {
272-
if event.Delta == "" || s.currentToolCall == nil {
326+
if event.Delta == "" {
273327
return nil
274328
}
275329

276-
// Accumulate arguments
277-
s.toolCallArgs += event.Delta
330+
toolCallIndex, ok := s.toolCallIndex(event)
331+
if !ok {
332+
return nil
333+
}
278334

279335
// Send delta
280336
return &relaymodel.ChatCompletionsStreamResponse{
281337
ID: s.messageID,
282338
Object: relaymodel.ChatCompletionChunkObject,
283-
Created: time.Now().Unix(),
339+
Created: s.createdAt(),
284340
Model: responseModelName(s.meta),
285341
Choices: []*relaymodel.ChatCompletionsStreamResponseChoice{
286342
{
287343
Index: 0,
288344
Delta: relaymodel.Message{
289345
ToolCalls: []relaymodel.ToolCall{
290346
{
291-
Index: 0,
347+
Index: toolCallIndex,
292348
Function: relaymodel.Function{
293349
Arguments: event.Delta,
294350
},
@@ -300,32 +356,6 @@ func (s *chatCompletionStreamState) handleFunctionCallArgumentsDelta(
300356
}
301357
}
302358

303-
// handleOutputItemDone handles response.output_item.done event for ChatCompletion
304-
func (s *chatCompletionStreamState) handleOutputItemDone(
305-
event *relaymodel.ResponseStreamEvent,
306-
) {
307-
if event.Item == nil {
308-
return
309-
}
310-
311-
// Handle function call completion
312-
if event.Item.Type == relaymodel.InputItemTypeFunctionCall && s.currentToolCall != nil &&
313-
event.Item.ID == s.currentToolCallID {
314-
// Update with final arguments
315-
if s.toolCallArgs != "" {
316-
s.currentToolCall.Function.Arguments = s.toolCallArgs
317-
}
318-
319-
// Reset state
320-
s.currentToolCall = nil
321-
s.currentToolCallID = ""
322-
s.toolCallArgs = ""
323-
324-
// No need to send another chunk - arguments already streamed
325-
return
326-
}
327-
}
328-
329359
// handleResponseCompleted handles response.completed/done event for ChatCompletion
330360
func (s *chatCompletionStreamState) handleResponseCompleted(
331361
event *relaymodel.ResponseStreamEvent,
@@ -344,7 +374,7 @@ func (s *chatCompletionStreamState) handleResponseCompleted(
344374
return &relaymodel.ChatCompletionsStreamResponse{
345375
ID: s.messageID,
346376
Object: relaymodel.ChatCompletionChunkObject,
347-
Created: time.Now().Unix(),
377+
Created: s.createdAt(),
348378
Model: responseModelName(s.meta),
349379
Choices: []*relaymodel.ChatCompletionsStreamResponseChoice{
350380
{
@@ -1341,6 +1371,8 @@ func ConvertResponsesToChatCompletionResponse(
13411371

13421372
reasonContent := responseReasoningSummaryText(&responsesResp)
13431373

1374+
var toolCallChoice *relaymodel.TextResponseChoice
1375+
13441376
// Convert output items to choices
13451377
for _, outputItem := range responsesResp.Output {
13461378
switch outputItem.Type {
@@ -1380,31 +1412,36 @@ func ConvertResponsesToChatCompletionResponse(
13801412
toolCallID = outputItem.ID
13811413
}
13821414

1383-
finishReason := responseToChatFinishReason(&responsesResp)
1384-
if finishReason == relaymodel.FinishReasonStop {
1385-
finishReason = relaymodel.FinishReasonToolCalls
1415+
if toolCallChoice == nil {
1416+
finishReason := responseToChatFinishReason(&responsesResp)
1417+
if finishReason == relaymodel.FinishReasonStop {
1418+
finishReason = relaymodel.FinishReasonToolCalls
1419+
}
1420+
1421+
toolCallChoice = &relaymodel.TextResponseChoice{
1422+
Index: len(chatResp.Choices),
1423+
Message: relaymodel.Message{
1424+
Role: relaymodel.RoleAssistant,
1425+
ReasoningContent: reasonContent,
1426+
},
1427+
FinishReason: finishReason,
1428+
}
1429+
chatResp.Choices = append(chatResp.Choices, toolCallChoice)
1430+
reasonContent = ""
13861431
}
13871432

1388-
chatResp.Choices = append(chatResp.Choices, &relaymodel.TextResponseChoice{
1389-
Index: len(chatResp.Choices),
1390-
Message: relaymodel.Message{
1391-
Role: relaymodel.RoleAssistant,
1392-
ReasoningContent: reasonContent,
1393-
ToolCalls: []relaymodel.ToolCall{
1394-
{
1395-
Index: 0,
1396-
ID: toolCallID,
1397-
Type: relaymodel.ToolChoiceTypeFunction,
1398-
Function: relaymodel.Function{
1399-
Name: outputItem.Name,
1400-
Arguments: outputItem.Arguments.String(),
1401-
},
1402-
},
1433+
toolCallChoice.Message.ToolCalls = append(
1434+
toolCallChoice.Message.ToolCalls,
1435+
relaymodel.ToolCall{
1436+
Index: len(toolCallChoice.Message.ToolCalls),
1437+
ID: toolCallID,
1438+
Type: relaymodel.ToolChoiceTypeFunction,
1439+
Function: relaymodel.Function{
1440+
Name: outputItem.Name,
1441+
Arguments: outputItem.Arguments.String(),
14031442
},
14041443
},
1405-
FinishReason: finishReason,
1406-
})
1407-
reasonContent = ""
1444+
)
14081445

14091446
default:
14101447
continue
@@ -1556,8 +1593,10 @@ func ConvertResponsesToChatCompletionStreamResponse(
15561593
errorState := responsesStreamErrorState{}
15571594

15581595
state := &chatCompletionStreamState{
1559-
meta: meta,
1560-
c: c,
1596+
meta: meta,
1597+
c: c,
1598+
toolCallIndexByItemID: make(map[string]int),
1599+
toolCallIndexByOutputIndex: make(map[int]int),
15611600
}
15621601
stopStream := false
15631602

@@ -1635,8 +1674,6 @@ func ConvertResponsesToChatCompletionStreamResponse(
16351674
chatStreamResp = state.handleOutputItemAdded(&event)
16361675
case relaymodel.EventFunctionCallArgumentsDelta:
16371676
chatStreamResp = state.handleFunctionCallArgumentsDelta(&event)
1638-
case relaymodel.EventOutputItemDone:
1639-
state.handleOutputItemDone(&event)
16401677
case relaymodel.EventResponseCompleted,
16411678
relaymodel.EventResponseIncomplete,
16421679
relaymodel.EventResponseDone:

0 commit comments

Comments
 (0)