2026-06-12 17:58:36 +08:00
|
|
|
|
//
|
|
|
|
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
|
//
|
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
//
|
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
package chunk
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
type SplitOperator struct {
|
feat(go): add length split strategy with overlap to chunk pipeline (#16047)
### What problem does this PR solve?
The Go ingestion chunk pipeline's `SplitOperator`
(`internal/ingestion/chunk/split.go`) supported only `sentence`, `char`,
and `paragraph` strategies, but not **fixed-size (length) chunking with
overlap** — the canonical RAG strategy for bounding chunk length while
preserving cross-boundary context.
This adds a `length` strategy alongside the existing ones, configurable
via DSL `params`:
- `chunk_size` — target window size in **runes** (rune-aware:
multi-byte/CJK text is windowed by character, never split mid-rune).
- `overlap` — runes carried from the end of each window into the next.
The window advances by `chunk_size - overlap`. `chunk_size` falls back
to a default (256) when unset/non-positive, and `overlap` is clamped to
`[0, chunk_size-1]` so the window always advances and the operation
terminates. Implementation follows the existing
`splitByChar`/`splitByParagraph` pattern and reuses `DetectLanguage` for
chunk metadata.
It also adds `split_test.go` — the first unit tests for the `chunk`
package — covering basic windowing, overlap, overlap
clamping/termination, rune-awareness (CJK), default sizing, no-overlap
reconstruction, empty input, and DSL param parsing.
Validation: `gofmt` clean, `go vet ./internal/ingestion/chunk/` clean,
`go build` ok, `go test ./internal/ingestion/chunk/` — all tests pass.
Closes #16046
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.com>
2026-07-06 13:14:21 +07:00
|
|
|
|
strategy string
|
|
|
|
|
|
boundaries []string
|
|
|
|
|
|
keepSeparators bool
|
|
|
|
|
|
chunkSize int
|
|
|
|
|
|
overlap int
|
2026-06-12 17:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
feat(go): add length split strategy with overlap to chunk pipeline (#16047)
### What problem does this PR solve?
The Go ingestion chunk pipeline's `SplitOperator`
(`internal/ingestion/chunk/split.go`) supported only `sentence`, `char`,
and `paragraph` strategies, but not **fixed-size (length) chunking with
overlap** — the canonical RAG strategy for bounding chunk length while
preserving cross-boundary context.
This adds a `length` strategy alongside the existing ones, configurable
via DSL `params`:
- `chunk_size` — target window size in **runes** (rune-aware:
multi-byte/CJK text is windowed by character, never split mid-rune).
- `overlap` — runes carried from the end of each window into the next.
The window advances by `chunk_size - overlap`. `chunk_size` falls back
to a default (256) when unset/non-positive, and `overlap` is clamped to
`[0, chunk_size-1]` so the window always advances and the operation
terminates. Implementation follows the existing
`splitByChar`/`splitByParagraph` pattern and reuses `DetectLanguage` for
chunk metadata.
It also adds `split_test.go` — the first unit tests for the `chunk`
package — covering basic windowing, overlap, overlap
clamping/termination, rune-awareness (CJK), default sizing, no-overlap
reconstruction, empty input, and DSL param parsing.
Validation: `gofmt` clean, `go vet ./internal/ingestion/chunk/` clean,
`go build` ok, `go test ./internal/ingestion/chunk/` — all tests pass.
Closes #16046
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.com>
2026-07-06 13:14:21 +07:00
|
|
|
|
// defaultLengthChunkSize is the window size (in runes) used by the "length"
|
|
|
|
|
|
// strategy when no positive chunk_size is configured.
|
|
|
|
|
|
const defaultLengthChunkSize = 256
|
|
|
|
|
|
|
2026-06-12 17:58:36 +08:00
|
|
|
|
func NewSplitOperator(config map[string]interface{}) (*SplitOperator, error) {
|
|
|
|
|
|
op := &SplitOperator{}
|
|
|
|
|
|
|
|
|
|
|
|
if v, ok := config["strategy"]; ok {
|
|
|
|
|
|
if s, ok := v.(string); ok {
|
|
|
|
|
|
op.strategy = s
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(go): add length split strategy with overlap to chunk pipeline (#16047)
### What problem does this PR solve?
The Go ingestion chunk pipeline's `SplitOperator`
(`internal/ingestion/chunk/split.go`) supported only `sentence`, `char`,
and `paragraph` strategies, but not **fixed-size (length) chunking with
overlap** — the canonical RAG strategy for bounding chunk length while
preserving cross-boundary context.
This adds a `length` strategy alongside the existing ones, configurable
via DSL `params`:
- `chunk_size` — target window size in **runes** (rune-aware:
multi-byte/CJK text is windowed by character, never split mid-rune).
- `overlap` — runes carried from the end of each window into the next.
The window advances by `chunk_size - overlap`. `chunk_size` falls back
to a default (256) when unset/non-positive, and `overlap` is clamped to
`[0, chunk_size-1]` so the window always advances and the operation
terminates. Implementation follows the existing
`splitByChar`/`splitByParagraph` pattern and reuses `DetectLanguage` for
chunk metadata.
It also adds `split_test.go` — the first unit tests for the `chunk`
package — covering basic windowing, overlap, overlap
clamping/termination, rune-awareness (CJK), default sizing, no-overlap
reconstruction, empty input, and DSL param parsing.
Validation: `gofmt` clean, `go vet ./internal/ingestion/chunk/` clean,
`go build` ok, `go test ./internal/ingestion/chunk/` — all tests pass.
Closes #16046
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.com>
2026-07-06 13:14:21 +07:00
|
|
|
|
if params, ok := config["params"].(map[string]interface{}); ok {
|
|
|
|
|
|
if b, ok := params["boundaries"]; ok {
|
|
|
|
|
|
if boundStrs, ok := b.([]interface{}); ok {
|
|
|
|
|
|
for _, bs := range boundStrs {
|
|
|
|
|
|
if s, ok := bs.(string); ok {
|
|
|
|
|
|
op.boundaries = append(op.boundaries, s)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if ks, ok := params["keep_separators"]; ok {
|
|
|
|
|
|
if b, ok := ks.(bool); ok {
|
|
|
|
|
|
op.keepSeparators = b
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// chunk_size / overlap drive the "length" strategy. JSON numbers
|
|
|
|
|
|
// decode as float64, so accept that alongside the integer types.
|
|
|
|
|
|
if cs, ok := params["chunk_size"]; ok {
|
|
|
|
|
|
op.chunkSize = toInt(cs)
|
|
|
|
|
|
}
|
|
|
|
|
|
if ov, ok := params["overlap"]; ok {
|
|
|
|
|
|
op.overlap = toInt(ov)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 17:58:36 +08:00
|
|
|
|
return op, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(go): add length split strategy with overlap to chunk pipeline (#16047)
### What problem does this PR solve?
The Go ingestion chunk pipeline's `SplitOperator`
(`internal/ingestion/chunk/split.go`) supported only `sentence`, `char`,
and `paragraph` strategies, but not **fixed-size (length) chunking with
overlap** — the canonical RAG strategy for bounding chunk length while
preserving cross-boundary context.
This adds a `length` strategy alongside the existing ones, configurable
via DSL `params`:
- `chunk_size` — target window size in **runes** (rune-aware:
multi-byte/CJK text is windowed by character, never split mid-rune).
- `overlap` — runes carried from the end of each window into the next.
The window advances by `chunk_size - overlap`. `chunk_size` falls back
to a default (256) when unset/non-positive, and `overlap` is clamped to
`[0, chunk_size-1]` so the window always advances and the operation
terminates. Implementation follows the existing
`splitByChar`/`splitByParagraph` pattern and reuses `DetectLanguage` for
chunk metadata.
It also adds `split_test.go` — the first unit tests for the `chunk`
package — covering basic windowing, overlap, overlap
clamping/termination, rune-awareness (CJK), default sizing, no-overlap
reconstruction, empty input, and DSL param parsing.
Validation: `gofmt` clean, `go vet ./internal/ingestion/chunk/` clean,
`go build` ok, `go test ./internal/ingestion/chunk/` — all tests pass.
Closes #16046
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.com>
2026-07-06 13:14:21 +07:00
|
|
|
|
// toInt coerces a DSL numeric value (float64 from JSON, or a native integer)
|
|
|
|
|
|
// to int. Any other type yields 0.
|
|
|
|
|
|
func toInt(v interface{}) int {
|
|
|
|
|
|
switch n := v.(type) {
|
|
|
|
|
|
case float64:
|
|
|
|
|
|
return int(n)
|
|
|
|
|
|
case int:
|
|
|
|
|
|
return n
|
|
|
|
|
|
case int64:
|
|
|
|
|
|
return int(n)
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 17:58:36 +08:00
|
|
|
|
func (o *SplitOperator) Prepare(ctx *ChunkContext) error {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (o *SplitOperator) Execute(ctx *ChunkContext) error {
|
|
|
|
|
|
text := ctx.TextAfterPreprocess
|
|
|
|
|
|
|
|
|
|
|
|
if o.strategy == "" {
|
|
|
|
|
|
o.strategy = "sentence"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch o.strategy {
|
|
|
|
|
|
case "sentence":
|
|
|
|
|
|
ctx.SplitChunks = o.splitSentences(text)
|
|
|
|
|
|
case "char":
|
|
|
|
|
|
ctx.SplitChunks = o.splitByChar(text)
|
|
|
|
|
|
case "paragraph":
|
|
|
|
|
|
ctx.SplitChunks = o.splitByParagraph(text)
|
feat(go): add length split strategy with overlap to chunk pipeline (#16047)
### What problem does this PR solve?
The Go ingestion chunk pipeline's `SplitOperator`
(`internal/ingestion/chunk/split.go`) supported only `sentence`, `char`,
and `paragraph` strategies, but not **fixed-size (length) chunking with
overlap** — the canonical RAG strategy for bounding chunk length while
preserving cross-boundary context.
This adds a `length` strategy alongside the existing ones, configurable
via DSL `params`:
- `chunk_size` — target window size in **runes** (rune-aware:
multi-byte/CJK text is windowed by character, never split mid-rune).
- `overlap` — runes carried from the end of each window into the next.
The window advances by `chunk_size - overlap`. `chunk_size` falls back
to a default (256) when unset/non-positive, and `overlap` is clamped to
`[0, chunk_size-1]` so the window always advances and the operation
terminates. Implementation follows the existing
`splitByChar`/`splitByParagraph` pattern and reuses `DetectLanguage` for
chunk metadata.
It also adds `split_test.go` — the first unit tests for the `chunk`
package — covering basic windowing, overlap, overlap
clamping/termination, rune-awareness (CJK), default sizing, no-overlap
reconstruction, empty input, and DSL param parsing.
Validation: `gofmt` clean, `go vet ./internal/ingestion/chunk/` clean,
`go build` ok, `go test ./internal/ingestion/chunk/` — all tests pass.
Closes #16046
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.com>
2026-07-06 13:14:21 +07:00
|
|
|
|
case "length":
|
|
|
|
|
|
ctx.SplitChunks = o.splitByLength(text)
|
2026-06-12 17:58:36 +08:00
|
|
|
|
default:
|
|
|
|
|
|
ctx.SplitChunks = o.splitSentences(text)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (o *SplitOperator) Finish(ctx *ChunkContext) error {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (o *SplitOperator) String() string {
|
|
|
|
|
|
var buf strings.Builder
|
|
|
|
|
|
buf.WriteString("split:\n")
|
|
|
|
|
|
fmt.Fprintf(&buf, " strategy: %q\n", o.strategy)
|
feat(go): add length split strategy with overlap to chunk pipeline (#16047)
### What problem does this PR solve?
The Go ingestion chunk pipeline's `SplitOperator`
(`internal/ingestion/chunk/split.go`) supported only `sentence`, `char`,
and `paragraph` strategies, but not **fixed-size (length) chunking with
overlap** — the canonical RAG strategy for bounding chunk length while
preserving cross-boundary context.
This adds a `length` strategy alongside the existing ones, configurable
via DSL `params`:
- `chunk_size` — target window size in **runes** (rune-aware:
multi-byte/CJK text is windowed by character, never split mid-rune).
- `overlap` — runes carried from the end of each window into the next.
The window advances by `chunk_size - overlap`. `chunk_size` falls back
to a default (256) when unset/non-positive, and `overlap` is clamped to
`[0, chunk_size-1]` so the window always advances and the operation
terminates. Implementation follows the existing
`splitByChar`/`splitByParagraph` pattern and reuses `DetectLanguage` for
chunk metadata.
It also adds `split_test.go` — the first unit tests for the `chunk`
package — covering basic windowing, overlap, overlap
clamping/termination, rune-awareness (CJK), default sizing, no-overlap
reconstruction, empty input, and DSL param parsing.
Validation: `gofmt` clean, `go vet ./internal/ingestion/chunk/` clean,
`go build` ok, `go test ./internal/ingestion/chunk/` — all tests pass.
Closes #16046
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.com>
2026-07-06 13:14:21 +07:00
|
|
|
|
fmt.Fprintf(&buf, " boundaries:\n")
|
|
|
|
|
|
for _, r := range o.boundaries {
|
|
|
|
|
|
fmt.Fprintf(&buf, " - %q\n", r)
|
|
|
|
|
|
}
|
|
|
|
|
|
fmt.Fprintf(&buf, " keep_separators: %t\n", o.keepSeparators)
|
|
|
|
|
|
fmt.Fprintf(&buf, " chunk_size: %d\n", o.chunkSize)
|
|
|
|
|
|
fmt.Fprintf(&buf, " overlap: %d\n", o.overlap)
|
2026-06-12 17:58:36 +08:00
|
|
|
|
return buf.String()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-05 20:43:52 +08:00
|
|
|
|
var sentenceBoundaries = []string{"。", "!", "?", ".", "!", "?", ";", "\n"}
|
2026-06-12 17:58:36 +08:00
|
|
|
|
|
2026-07-05 20:43:52 +08:00
|
|
|
|
// splitSentences splits text at the built-in sentence boundaries.
|
|
|
|
|
|
func (o *SplitOperator) splitSentences(text string) []ChunkData {
|
2026-06-12 17:58:36 +08:00
|
|
|
|
var chunks []ChunkData
|
|
|
|
|
|
var buf strings.Builder
|
|
|
|
|
|
i := 0
|
|
|
|
|
|
|
|
|
|
|
|
for i < len(text) {
|
|
|
|
|
|
// Try to match any boundary at current position (first match wins)
|
|
|
|
|
|
matchedBound := ""
|
2026-07-05 20:43:52 +08:00
|
|
|
|
for _, bound := range sentenceBoundaries {
|
2026-06-12 17:58:36 +08:00
|
|
|
|
if bound != "" && i+len(bound) <= len(text) && text[i:i+len(bound)] == bound {
|
|
|
|
|
|
matchedBound = bound
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if matchedBound != "" {
|
|
|
|
|
|
if buf.Len() > 0 {
|
2026-07-05 20:43:52 +08:00
|
|
|
|
content := strings.TrimSpace(buf.String())
|
|
|
|
|
|
if content != "" {
|
|
|
|
|
|
chunks = append(chunks, ChunkData{
|
|
|
|
|
|
Content: content,
|
|
|
|
|
|
Index: len(chunks),
|
|
|
|
|
|
Metadata: map[string]interface{}{
|
|
|
|
|
|
"language": DetectLanguage(content),
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-06-12 17:58:36 +08:00
|
|
|
|
buf.Reset()
|
|
|
|
|
|
}
|
|
|
|
|
|
i += len(matchedBound)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
r, size := utf8.DecodeRuneInString(text[i:])
|
|
|
|
|
|
buf.WriteRune(r)
|
|
|
|
|
|
i += size
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// flush remaining text
|
|
|
|
|
|
if buf.Len() > 0 {
|
2026-07-05 20:43:52 +08:00
|
|
|
|
content := strings.TrimSpace(buf.String())
|
|
|
|
|
|
if content != "" {
|
|
|
|
|
|
chunks = append(chunks, ChunkData{
|
|
|
|
|
|
Content: content,
|
|
|
|
|
|
Index: len(chunks),
|
|
|
|
|
|
Metadata: map[string]interface{}{
|
|
|
|
|
|
"language": DetectLanguage(content),
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-06-12 17:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return chunks
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (o *SplitOperator) splitByChar(text string) []ChunkData {
|
|
|
|
|
|
var chunks []ChunkData
|
|
|
|
|
|
for len(text) > 0 {
|
|
|
|
|
|
r, size := utf8.DecodeRuneInString(text)
|
|
|
|
|
|
chunks = append(chunks, ChunkData{
|
|
|
|
|
|
Content: string(r),
|
|
|
|
|
|
Index: len(chunks),
|
|
|
|
|
|
Metadata: map[string]interface{}{"language": DetectLanguage(text)},
|
|
|
|
|
|
})
|
|
|
|
|
|
text = text[size:]
|
|
|
|
|
|
}
|
|
|
|
|
|
return chunks
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(go): add length split strategy with overlap to chunk pipeline (#16047)
### What problem does this PR solve?
The Go ingestion chunk pipeline's `SplitOperator`
(`internal/ingestion/chunk/split.go`) supported only `sentence`, `char`,
and `paragraph` strategies, but not **fixed-size (length) chunking with
overlap** — the canonical RAG strategy for bounding chunk length while
preserving cross-boundary context.
This adds a `length` strategy alongside the existing ones, configurable
via DSL `params`:
- `chunk_size` — target window size in **runes** (rune-aware:
multi-byte/CJK text is windowed by character, never split mid-rune).
- `overlap` — runes carried from the end of each window into the next.
The window advances by `chunk_size - overlap`. `chunk_size` falls back
to a default (256) when unset/non-positive, and `overlap` is clamped to
`[0, chunk_size-1]` so the window always advances and the operation
terminates. Implementation follows the existing
`splitByChar`/`splitByParagraph` pattern and reuses `DetectLanguage` for
chunk metadata.
It also adds `split_test.go` — the first unit tests for the `chunk`
package — covering basic windowing, overlap, overlap
clamping/termination, rune-awareness (CJK), default sizing, no-overlap
reconstruction, empty input, and DSL param parsing.
Validation: `gofmt` clean, `go vet ./internal/ingestion/chunk/` clean,
`go build` ok, `go test ./internal/ingestion/chunk/` — all tests pass.
Closes #16046
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.com>
2026-07-06 13:14:21 +07:00
|
|
|
|
// splitByLength splits text into fixed-size, rune-aware windows of chunkSize
|
|
|
|
|
|
// runes, carrying overlap runes from the end of each window into the start of
|
|
|
|
|
|
// the next. This is the canonical fixed-size-with-overlap chunking used by RAG
|
|
|
|
|
|
// pipelines to bound chunk length while preserving cross-boundary context.
|
|
|
|
|
|
//
|
|
|
|
|
|
// chunkSize defaults to defaultLengthChunkSize when not positive. overlap is
|
|
|
|
|
|
// clamped to [0, chunkSize-1] so the window always advances by at least one
|
|
|
|
|
|
// rune and the function terminates. Sizing is by rune count (not bytes), so
|
|
|
|
|
|
// multi-byte (e.g. CJK) text is windowed by character.
|
|
|
|
|
|
func (o *SplitOperator) splitByLength(text string) []ChunkData {
|
|
|
|
|
|
chunkSize := o.chunkSize
|
|
|
|
|
|
if chunkSize <= 0 {
|
|
|
|
|
|
chunkSize = defaultLengthChunkSize
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
overlap := o.overlap
|
|
|
|
|
|
if overlap < 0 {
|
|
|
|
|
|
overlap = 0
|
|
|
|
|
|
}
|
|
|
|
|
|
if overlap >= chunkSize {
|
|
|
|
|
|
overlap = chunkSize - 1
|
|
|
|
|
|
}
|
|
|
|
|
|
step := chunkSize - overlap
|
|
|
|
|
|
|
|
|
|
|
|
runes := []rune(text)
|
|
|
|
|
|
if len(runes) == 0 {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var chunks []ChunkData
|
|
|
|
|
|
for start := 0; start < len(runes); start += step {
|
|
|
|
|
|
end := start + chunkSize
|
|
|
|
|
|
if end > len(runes) {
|
|
|
|
|
|
end = len(runes)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
content := string(runes[start:end])
|
|
|
|
|
|
chunks = append(chunks, ChunkData{
|
|
|
|
|
|
Content: content,
|
|
|
|
|
|
Size: end - start,
|
|
|
|
|
|
Index: len(chunks),
|
|
|
|
|
|
Metadata: map[string]interface{}{
|
|
|
|
|
|
"language": DetectLanguage(content),
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if end == len(runes) {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return chunks
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 17:58:36 +08:00
|
|
|
|
func (o *SplitOperator) splitByParagraph(text string) []ChunkData {
|
|
|
|
|
|
paragraphs := strings.Split(text, "\n")
|
|
|
|
|
|
chunks := make([]ChunkData, 0, len(paragraphs))
|
|
|
|
|
|
for i, p := range paragraphs {
|
|
|
|
|
|
trimmed := strings.TrimSpace(p)
|
|
|
|
|
|
if trimmed == "" {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
chunks = append(chunks, ChunkData{
|
|
|
|
|
|
Content: trimmed,
|
|
|
|
|
|
Index: i,
|
|
|
|
|
|
Metadata: map[string]interface{}{"language": DetectLanguage(trimmed)},
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return chunks
|
|
|
|
|
|
}
|