Files
ragflow/internal/harness/graph/channels/reducer.go

172 lines
4.7 KiB
Go

package channels
import (
"reflect"
"ragflow/internal/harness/graph/types"
)
// ReducerChannel wraps a channel with a reducer function.
type ReducerChannel struct {
Channel
reducer types.ReducerFunc
}
// NewReducerChannel creates a new ReducerChannel.
func NewReducerChannel(channel Channel, reducer types.ReducerFunc) *ReducerChannel {
return &ReducerChannel{
Channel: channel,
reducer: reducer,
}
}
// Update applies the reducer to combine new values with the current value.
func (rc *ReducerChannel) Update(values []interface{}) (bool, error) {
if len(values) == 0 {
return false, nil
}
// Read current value from the wrapped channel.
current, err := rc.Channel.Get()
// Combine all values with the current value using the reducer.
// If the channel is empty, start from values[0] and combine the rest.
combined := values[0]
if err == nil {
combined = rc.reducer(current, combined)
}
for i := 1; i < len(values); i++ {
combined = rc.reducer(combined, values[i])
}
return rc.Channel.Update([]interface{}{combined})
}
// Copy returns a copy of the ReducerChannel.
func (rc *ReducerChannel) Copy() Channel {
return &ReducerChannel{
Channel: rc.Channel.Copy(),
reducer: rc.reducer,
}
}
// Checkpoint returns the checkpoint of the wrapped channel.
func (rc *ReducerChannel) Checkpoint() interface{} {
return rc.Channel.Checkpoint()
}
// FromCheckpoint restores the wrapped channel from a checkpoint.
func (rc *ReducerChannel) FromCheckpoint(checkpoint interface{}) Channel {
return &ReducerChannel{
Channel: rc.Channel.FromCheckpoint(checkpoint),
reducer: rc.reducer,
}
}
// CreateReducerChannel creates a reducer channel based on type hints.
// It inspects the field type and annotation to determine appropriate channel.
func CreateReducerChannel(fieldName string, fieldType reflect.Type, reducer types.ReducerFunc) (Channel, error) {
// Determine default channel based on type
var channel Channel
switch fieldType.Kind() {
case reflect.Slice, reflect.Array:
// For slices, use BinaryOperatorAggregate with append operator
channel = NewBinaryOperatorAggregate(fieldType, ListAppend)
case reflect.Map:
// For maps, use BinaryOperatorAggregate with merge operator
channel = NewBinaryOperatorAggregate(fieldType, func(a, b interface{}) interface{} {
if aMap, ok := a.(map[string]interface{}); ok {
if bMap, ok := b.(map[string]interface{}); ok {
result := make(map[string]interface{}, len(aMap)+len(bMap))
for k, v := range aMap {
result[k] = v
}
for k, v := range bMap {
result[k] = v
}
return result
}
}
return b
})
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
// For numeric types, use BinaryOperatorAggregate with add operator.
// Pass reflect.Zero(fieldType).Interface() instead of fieldType itself
// so createZeroValue uses the correct underlying numeric type for its
// reflect.Zero call, not the reflect.Type descriptor.
channel = NewBinaryOperatorAggregate(reflect.Zero(fieldType).Interface(), IntAdd)
default:
// Default to LastValue channel
channel = NewLastValue(fieldType)
}
// Set the channel key
channel.SetKey(fieldName)
// If a reducer is provided, wrap the channel with it
if reducer != nil {
return NewReducerChannel(channel, reducer), nil
}
return channel, nil
}
// Built-in reducer functions for common types.
var (
// AddReducer adds numeric values.
AddReducer = func(current, update interface{}) interface{} {
if current == nil {
return update
}
// Try int addition
if ci, ok := current.(int); ok {
if ui, ok := update.(int); ok {
return ci + ui
}
}
// Try float64 addition
if cf, ok := current.(float64); ok {
if uf, ok := update.(float64); ok {
return cf + uf
}
}
// Fallback: return update
return update
}
// AppendReducer appends to slices.
AppendReducer = func(current, update interface{}) interface{} {
if current == nil {
return []interface{}{update}
}
if slice, ok := current.([]interface{}); ok {
return append(slice, update)
}
// Convert to slice
return []interface{}{current, update}
}
// MergeReducer merges maps.
MergeReducer = func(current, update interface{}) interface{} {
if current == nil {
return update
}
if currentMap, ok := current.(map[string]interface{}); ok {
if updateMap, ok := update.(map[string]interface{}); ok {
result := make(map[string]interface{}, len(currentMap)+len(updateMap))
for k, v := range currentMap {
result[k] = v
}
for k, v := range updateMap {
result[k] = v
}
return result
}
}
return update
}
)