mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
feat(translator): add reasoning state tracking and improve reasoning summary handling
- Introduced `oaiToResponsesStateReasoning` to track reasoning data. - Enhanced logic for emitting reasoning summary events and managing state transitions. - Updated output generation to handle multiple reasoning entries consistently.
This commit is contained in:
@@ -12,6 +12,10 @@ import (
|
|||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type oaiToResponsesStateReasoning struct {
|
||||||
|
ReasoningID string
|
||||||
|
ReasoningData string
|
||||||
|
}
|
||||||
type oaiToResponsesState struct {
|
type oaiToResponsesState struct {
|
||||||
Seq int
|
Seq int
|
||||||
ResponseID string
|
ResponseID string
|
||||||
@@ -23,6 +27,7 @@ type oaiToResponsesState struct {
|
|||||||
// Per-output message text buffers by index
|
// Per-output message text buffers by index
|
||||||
MsgTextBuf map[int]*strings.Builder
|
MsgTextBuf map[int]*strings.Builder
|
||||||
ReasoningBuf strings.Builder
|
ReasoningBuf strings.Builder
|
||||||
|
Reasonings []oaiToResponsesStateReasoning
|
||||||
FuncArgsBuf map[int]*strings.Builder // index -> args
|
FuncArgsBuf map[int]*strings.Builder // index -> args
|
||||||
FuncNames map[int]string // index -> name
|
FuncNames map[int]string // index -> name
|
||||||
FuncCallIDs map[int]string // index -> call_id
|
FuncCallIDs map[int]string // index -> call_id
|
||||||
@@ -63,6 +68,7 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context,
|
|||||||
MsgItemDone: make(map[int]bool),
|
MsgItemDone: make(map[int]bool),
|
||||||
FuncArgsDone: make(map[int]bool),
|
FuncArgsDone: make(map[int]bool),
|
||||||
FuncItemDone: make(map[int]bool),
|
FuncItemDone: make(map[int]bool),
|
||||||
|
Reasonings: make([]oaiToResponsesStateReasoning, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
st := (*param).(*oaiToResponsesState)
|
st := (*param).(*oaiToResponsesState)
|
||||||
@@ -157,6 +163,31 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context,
|
|||||||
st.Started = true
|
st.Started = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopReasoning := func(text string) {
|
||||||
|
// Emit reasoning done events
|
||||||
|
textDone := `{"type":"response.reasoning_summary_text.done","sequence_number":0,"item_id":"","output_index":0,"summary_index":0,"text":""}`
|
||||||
|
textDone, _ = sjson.Set(textDone, "sequence_number", nextSeq())
|
||||||
|
textDone, _ = sjson.Set(textDone, "item_id", st.ReasoningID)
|
||||||
|
textDone, _ = sjson.Set(textDone, "output_index", st.ReasoningIndex)
|
||||||
|
textDone, _ = sjson.Set(textDone, "text", text)
|
||||||
|
out = append(out, emitRespEvent("response.reasoning_summary_text.done", textDone))
|
||||||
|
partDone := `{"type":"response.reasoning_summary_part.done","sequence_number":0,"item_id":"","output_index":0,"summary_index":0,"part":{"type":"summary_text","text":""}}`
|
||||||
|
partDone, _ = sjson.Set(partDone, "sequence_number", nextSeq())
|
||||||
|
partDone, _ = sjson.Set(partDone, "item_id", st.ReasoningID)
|
||||||
|
partDone, _ = sjson.Set(partDone, "output_index", st.ReasoningIndex)
|
||||||
|
partDone, _ = sjson.Set(partDone, "part.text", text)
|
||||||
|
out = append(out, emitRespEvent("response.reasoning_summary_part.done", partDone))
|
||||||
|
outputItemDone := `{"type":"response.output_item.done","item":{"id":"","type":"reasoning","encrypted_content":"","summary":[{"type":"summary_text","text":""}]},"output_index":0,"sequence_number":0}`
|
||||||
|
outputItemDone, _ = sjson.Set(outputItemDone, "sequence_number", nextSeq())
|
||||||
|
outputItemDone, _ = sjson.Set(outputItemDone, "item.id", st.ReasoningID)
|
||||||
|
outputItemDone, _ = sjson.Set(outputItemDone, "output_index", st.ReasoningIndex)
|
||||||
|
outputItemDone, _ = sjson.Set(outputItemDone, "item.summary.text", text)
|
||||||
|
out = append(out, emitRespEvent("response.output_item.done", outputItemDone))
|
||||||
|
|
||||||
|
st.Reasonings = append(st.Reasonings, oaiToResponsesStateReasoning{ReasoningID: st.ReasoningID, ReasoningData: text})
|
||||||
|
st.ReasoningID = ""
|
||||||
|
}
|
||||||
|
|
||||||
// choices[].delta content / tool_calls / reasoning_content
|
// choices[].delta content / tool_calls / reasoning_content
|
||||||
if choices := root.Get("choices"); choices.Exists() && choices.IsArray() {
|
if choices := root.Get("choices"); choices.Exists() && choices.IsArray() {
|
||||||
choices.ForEach(func(_, choice gjson.Result) bool {
|
choices.ForEach(func(_, choice gjson.Result) bool {
|
||||||
@@ -165,6 +196,10 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context,
|
|||||||
if delta.Exists() {
|
if delta.Exists() {
|
||||||
if c := delta.Get("content"); c.Exists() && c.String() != "" {
|
if c := delta.Get("content"); c.Exists() && c.String() != "" {
|
||||||
// Ensure the message item and its first content part are announced before any text deltas
|
// Ensure the message item and its first content part are announced before any text deltas
|
||||||
|
if st.ReasoningID != "" {
|
||||||
|
stopReasoning(st.ReasoningBuf.String())
|
||||||
|
st.ReasoningBuf.Reset()
|
||||||
|
}
|
||||||
if !st.MsgItemAdded[idx] {
|
if !st.MsgItemAdded[idx] {
|
||||||
item := `{"type":"response.output_item.added","sequence_number":0,"output_index":0,"item":{"id":"","type":"message","status":"in_progress","content":[],"role":"assistant"}}`
|
item := `{"type":"response.output_item.added","sequence_number":0,"output_index":0,"item":{"id":"","type":"message","status":"in_progress","content":[],"role":"assistant"}}`
|
||||||
item, _ = sjson.Set(item, "sequence_number", nextSeq())
|
item, _ = sjson.Set(item, "sequence_number", nextSeq())
|
||||||
@@ -226,6 +261,10 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context,
|
|||||||
|
|
||||||
// tool calls
|
// tool calls
|
||||||
if tcs := delta.Get("tool_calls"); tcs.Exists() && tcs.IsArray() {
|
if tcs := delta.Get("tool_calls"); tcs.Exists() && tcs.IsArray() {
|
||||||
|
if st.ReasoningID != "" {
|
||||||
|
stopReasoning(st.ReasoningBuf.String())
|
||||||
|
st.ReasoningBuf.Reset()
|
||||||
|
}
|
||||||
// Before emitting any function events, if a message is open for this index,
|
// Before emitting any function events, if a message is open for this index,
|
||||||
// close its text/content to match Codex expected ordering.
|
// close its text/content to match Codex expected ordering.
|
||||||
if st.MsgItemAdded[idx] && !st.MsgItemDone[idx] {
|
if st.MsgItemAdded[idx] && !st.MsgItemDone[idx] {
|
||||||
@@ -361,17 +400,8 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if st.ReasoningID != "" {
|
if st.ReasoningID != "" {
|
||||||
// Emit reasoning done events
|
stopReasoning(st.ReasoningBuf.String())
|
||||||
textDone := `{"type":"response.reasoning_summary_text.done","sequence_number":0,"item_id":"","output_index":0,"summary_index":0,"text":""}`
|
st.ReasoningBuf.Reset()
|
||||||
textDone, _ = sjson.Set(textDone, "sequence_number", nextSeq())
|
|
||||||
textDone, _ = sjson.Set(textDone, "item_id", st.ReasoningID)
|
|
||||||
textDone, _ = sjson.Set(textDone, "output_index", st.ReasoningIndex)
|
|
||||||
out = append(out, emitRespEvent("response.reasoning_summary_text.done", textDone))
|
|
||||||
partDone := `{"type":"response.reasoning_summary_part.done","sequence_number":0,"item_id":"","output_index":0,"summary_index":0,"part":{"type":"summary_text","text":""}}`
|
|
||||||
partDone, _ = sjson.Set(partDone, "sequence_number", nextSeq())
|
|
||||||
partDone, _ = sjson.Set(partDone, "item_id", st.ReasoningID)
|
|
||||||
partDone, _ = sjson.Set(partDone, "output_index", st.ReasoningIndex)
|
|
||||||
out = append(out, emitRespEvent("response.reasoning_summary_part.done", partDone))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit function call done events for any active function calls
|
// Emit function call done events for any active function calls
|
||||||
@@ -485,11 +515,13 @@ func ConvertOpenAIChatCompletionsResponseToOpenAIResponses(ctx context.Context,
|
|||||||
}
|
}
|
||||||
// Build response.output using aggregated buffers
|
// Build response.output using aggregated buffers
|
||||||
outputsWrapper := `{"arr":[]}`
|
outputsWrapper := `{"arr":[]}`
|
||||||
if st.ReasoningBuf.Len() > 0 {
|
if len(st.Reasonings) > 0 {
|
||||||
item := `{"id":"","type":"reasoning","summary":[{"type":"summary_text","text":""}]}`
|
for _, r := range st.Reasonings {
|
||||||
item, _ = sjson.Set(item, "id", st.ReasoningID)
|
item := `{"id":"","type":"reasoning","summary":[{"type":"summary_text","text":""}]}`
|
||||||
item, _ = sjson.Set(item, "summary.0.text", st.ReasoningBuf.String())
|
item, _ = sjson.Set(item, "id", r.ReasoningID)
|
||||||
outputsWrapper, _ = sjson.SetRaw(outputsWrapper, "arr.-1", item)
|
item, _ = sjson.Set(item, "summary.0.text", r.ReasoningData)
|
||||||
|
outputsWrapper, _ = sjson.SetRaw(outputsWrapper, "arr.-1", item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Append message items in ascending index order
|
// Append message items in ascending index order
|
||||||
if len(st.MsgItemAdded) > 0 {
|
if len(st.MsgItemAdded) > 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user