Skip to content

Commit 8267766

Browse files
committed
feat(schema): wire OpenAI ReasoningExtension into Reasoning block
1 parent 30f5961 commit 8267766

4 files changed

Lines changed: 138 additions & 6 deletions

File tree

adk/middlewares/summarization/finalizer_builder.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ type TypedFinalizerBuilder[M adk.MessageType] struct {
6060

6161
// FinalizerBuilder is a backward-compatible alias for TypedFinalizerBuilder
6262
// specialized with *schema.Message.
63-
//
64-
// Deprecated: use TypedFinalizerBuilder[*schema.Message] instead.
6563
type FinalizerBuilder = TypedFinalizerBuilder[*schema.Message]
6664

6765
// NewTypedFinalizer creates a new TypedFinalizerBuilder.
@@ -88,8 +86,6 @@ func NewTypedFinalizer[M adk.MessageType]() *TypedFinalizerBuilder[M] {
8886

8987
// NewFinalizer creates a new FinalizerBuilder that builds a FinalizeFunc
9088
// by chaining handlers.
91-
//
92-
// Deprecated: use NewTypedFinalizer[*schema.Message] instead.
9389
func NewFinalizer() *FinalizerBuilder {
9490
return &FinalizerBuilder{}
9591
}

schema/agentic_message.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@ type Reasoning struct {
285285
// Signature contains encrypted reasoning tokens.
286286
// Required by some models when passing reasoning text back.
287287
Signature string `json:"signature,omitempty"`
288+
289+
// OpenAIExtension is the extension for OpenAI.
290+
OpenAIExtension *openai.ReasoningExtension `json:"openai_extension,omitempty"`
288291
}
289292

290293
type FunctionToolCall struct {
@@ -1303,20 +1306,35 @@ func genericGetTFromContentBlocks[T any](blocks []*ContentBlock, checkAndGetter
13031306
return ret, nil
13041307
}
13051308

1306-
func concatReasoning(reasons []*Reasoning) (*Reasoning, error) {
1309+
func concatReasoning(reasons []*Reasoning) (ret *Reasoning, err error) {
13071310
if len(reasons) == 0 {
13081311
return nil, fmt.Errorf("no reasoning found")
13091312
}
13101313

1311-
ret := &Reasoning{}
1314+
ret = &Reasoning{}
1315+
1316+
openaiExtensions := make([]*openai.ReasoningExtension, 0, len(reasons))
13121317

13131318
for _, r := range reasons {
1319+
if r == nil {
1320+
continue
1321+
}
13141322
if r.Text != "" {
13151323
ret.Text += r.Text
13161324
}
13171325
if r.Signature != "" {
13181326
ret.Signature += r.Signature
13191327
}
1328+
if r.OpenAIExtension != nil {
1329+
openaiExtensions = append(openaiExtensions, r.OpenAIExtension)
1330+
}
1331+
}
1332+
1333+
if len(openaiExtensions) > 0 {
1334+
ret.OpenAIExtension, err = openai.ConcatReasoningExtensions(openaiExtensions)
1335+
if err != nil {
1336+
return nil, fmt.Errorf("failed to concat openai reasoning extensions: %w", err)
1337+
}
13201338
}
13211339

13221340
return ret, nil

schema/openai/extension.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ type AssistantGenTextExtension struct {
3838
Annotations []*TextAnnotation `json:"annotations,omitempty"`
3939
}
4040

41+
type ReasoningExtension struct {
42+
// Content is the reasoning text content.
43+
Content []*ReasoningContent `json:"content,omitempty"`
44+
}
45+
46+
type ReasoningContent struct {
47+
Text string `json:"text,omitempty"`
48+
49+
// Index specifies the index position of this content in the final response.
50+
Index *int `json:"index,omitempty"`
51+
}
52+
4153
type ResponseError struct {
4254
Code ResponseErrorCode `json:"code,omitempty"`
4355
Message string `json:"message,omitempty"`
@@ -167,6 +179,53 @@ func ConcatAssistantGenTextExtensions(chunks []*AssistantGenTextExtension) (*Ass
167179
return ret, nil
168180
}
169181

182+
// ConcatReasoningExtensions concatenates multiple ReasoningExtension chunks into a single one.
183+
func ConcatReasoningExtensions(chunks []*ReasoningExtension) (*ReasoningExtension, error) {
184+
if len(chunks) == 0 {
185+
return nil, fmt.Errorf("no reasoning extension found")
186+
}
187+
188+
ret := &ReasoningExtension{}
189+
190+
var (
191+
indices []int
192+
indexToContent = map[int]*ReasoningContent{}
193+
)
194+
195+
for _, ext := range chunks {
196+
if ext == nil {
197+
continue
198+
}
199+
for _, c := range ext.Content {
200+
if c == nil {
201+
continue
202+
}
203+
if c.Index == nil {
204+
return nil, fmt.Errorf("reasoning content chunk missing index")
205+
}
206+
207+
idx := *c.Index
208+
if existing, ok := indexToContent[idx]; ok {
209+
existing.Text += c.Text
210+
} else {
211+
indexToContent[idx] = &ReasoningContent{
212+
Text: c.Text,
213+
}
214+
indices = append(indices, idx)
215+
}
216+
}
217+
}
218+
219+
sort.Ints(indices)
220+
221+
ret.Content = make([]*ReasoningContent, 0, len(indices))
222+
for _, idx := range indices {
223+
ret.Content = append(ret.Content, indexToContent[idx])
224+
}
225+
226+
return ret, nil
227+
}
228+
170229
// ConcatResponseMetaExtensions concatenates multiple ResponseMetaExtension chunks into a single one.
171230
func ConcatResponseMetaExtensions(chunks []*ResponseMetaExtension) (*ResponseMetaExtension, error) {
172231
if len(chunks) == 0 {

schema/openai/extension_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"testing"
2121

2222
"github.com/stretchr/testify/assert"
23+
24+
"github.com/cloudwego/eino/internal/generic"
2325
)
2426

2527
func TestConcatResponseMetaExtensions(t *testing.T) {
@@ -191,3 +193,60 @@ func TestConcatAssistantGenTextExtensions(t *testing.T) {
191193
assert.Error(t, err)
192194
})
193195
}
196+
197+
func TestConcatReasoningExtensions(t *testing.T) {
198+
t.Run("empty chunks - error", func(t *testing.T) {
199+
_, err := ConcatReasoningExtensions(nil)
200+
assert.Error(t, err)
201+
})
202+
203+
t.Run("single extension", func(t *testing.T) {
204+
ext := &ReasoningExtension{
205+
Content: []*ReasoningContent{
206+
{Text: "hello", Index: generic.PtrOf(0)},
207+
},
208+
}
209+
210+
result, err := ConcatReasoningExtensions([]*ReasoningExtension{ext})
211+
assert.NoError(t, err)
212+
assert.Len(t, result.Content, 1)
213+
assert.Equal(t, "hello", result.Content[0].Text)
214+
})
215+
216+
t.Run("streaming scenario - same index concatenated, ordered by index", func(t *testing.T) {
217+
exts := []*ReasoningExtension{
218+
{Content: []*ReasoningContent{{Text: "he", Index: generic.PtrOf(0)}}},
219+
{Content: []*ReasoningContent{{Text: "first", Index: generic.PtrOf(1)}}},
220+
{Content: []*ReasoningContent{{Text: "llo", Index: generic.PtrOf(0)}}},
221+
}
222+
223+
result, err := ConcatReasoningExtensions(exts)
224+
assert.NoError(t, err)
225+
assert.Len(t, result.Content, 2)
226+
assert.Nil(t, result.Content[0].Index)
227+
assert.Equal(t, "hello", result.Content[0].Text)
228+
assert.Nil(t, result.Content[1].Index)
229+
assert.Equal(t, "first", result.Content[1].Text)
230+
})
231+
232+
t.Run("nil extension and nil content skipped", func(t *testing.T) {
233+
exts := []*ReasoningExtension{
234+
nil,
235+
{Content: []*ReasoningContent{nil, {Text: "ok", Index: generic.PtrOf(0)}}},
236+
}
237+
238+
result, err := ConcatReasoningExtensions(exts)
239+
assert.NoError(t, err)
240+
assert.Len(t, result.Content, 1)
241+
assert.Equal(t, "ok", result.Content[0].Text)
242+
})
243+
244+
t.Run("missing index - error", func(t *testing.T) {
245+
exts := []*ReasoningExtension{
246+
{Content: []*ReasoningContent{{Text: "no meta"}}},
247+
}
248+
249+
_, err := ConcatReasoningExtensions(exts)
250+
assert.Error(t, err)
251+
})
252+
}

0 commit comments

Comments
 (0)