Skip to content

Commit b9e6a61

Browse files
authored
fix: Kiwi v0.23.0 지원 및 Functional Options 패턴 적용 (Resolves #41) (#42)
* feat: update Kiwi to v0.23.0 and implement Functional Options API This commit addresses the API breaking changes introduced in Kiwi v0.23.0 where new parameters like `enabled_dialects` and structs like `kiwi_analyze_option_t` were introduced. To resolve compilation failures and provide a clean idiomatic Go interface, the API was refactored to use the Functional Options pattern. Changes include: - Updated Makefile to download Kiwi v0.23.0 - Refactored `New` and `NewBuilder` to use `Option` callbacks. - Refactored `Analyze` to use `AnalyzeOptionFunc` callbacks, defaulting TopN to 1. - Updated all test cases to reflect the new API and new model scoring weights. * refactor: separate Dialect constants from default configuration variables * chore: update Go version to 1.26 * style: format kiwi.go constants and arguments * test: ignore float Score in tests due to ARM64 cross-platform quantization differences
1 parent 8eb509f commit b9e6a61

6 files changed

Lines changed: 183 additions & 46 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
strategy:
1010
matrix:
1111
go-version:
12-
- '1.23'
12+
- '1.26'
1313
os:
1414
- ubuntu-latest
1515
- macos-latest

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
KIWI_VERSION := v0.21.0
1+
KIWI_VERSION := v0.23.0
22

33
.PHONY: test
44
test: base/default.dict
@@ -7,7 +7,8 @@ test: base/default.dict
77
base/default.dict:
88
curl -L https://github.com/bab2min/Kiwi/releases/download/$(KIWI_VERSION)/kiwi_model_$(KIWI_VERSION)_base.tgz --output model.tgz
99
tar --no-same-owner -xzvf model.tgz
10-
rm -f model.tgz
10+
mv models/cong/base ./base
11+
rm -rf models model.tgz
1112

1213

1314
.PHONY: install-kiwi

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/codingpot/kiwigo
22

3-
go 1.23
3+
go 1.26
44

55
require (
66
github.com/google/go-cmp v0.6.0
@@ -12,4 +12,3 @@ require (
1212
github.com/pmezard/go-difflib v1.0.0 // indirect
1313
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
1414
)
15-

kiwi.go

Lines changed: 155 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,127 @@ const (
3333
KIWI_BUILD_DEFAULT BuildOption = C.KIWI_BUILD_DEFAULT
3434
)
3535

36-
// AnalyzeOption is a bitwise OR of the KiwiAnalyzeOption values.
37-
type AnalyzeOption int
36+
// MatchOption is a bitwise OR of the KiwiMatchOption values.
37+
type MatchOption int
3838

3939
const (
40-
KIWI_MATCH_URL AnalyzeOption = C.KIWI_MATCH_URL
41-
KIWI_MATCH_EMAIL AnalyzeOption = C.KIWI_MATCH_EMAIL
42-
KIWI_MATCH_HASHTAG AnalyzeOption = C.KIWI_MATCH_HASHTAG
43-
KIWI_MATCH_MENTION AnalyzeOption = C.KIWI_MATCH_MENTION
44-
KIWI_MATCH_ALL AnalyzeOption = C.KIWI_MATCH_ALL
45-
KIWI_MATCH_NORMALIZE_CODA AnalyzeOption = C.KIWI_MATCH_NORMALIZE_CODA
46-
KIWI_MATCH_ALL_WITH_NORMALIZING AnalyzeOption = C.KIWI_MATCH_ALL_WITH_NORMALIZING
40+
KIWI_MATCH_URL MatchOption = C.KIWI_MATCH_URL
41+
KIWI_MATCH_EMAIL MatchOption = C.KIWI_MATCH_EMAIL
42+
KIWI_MATCH_HASHTAG MatchOption = C.KIWI_MATCH_HASHTAG
43+
KIWI_MATCH_MENTION MatchOption = C.KIWI_MATCH_MENTION
44+
KIWI_MATCH_ALL MatchOption = C.KIWI_MATCH_ALL
45+
KIWI_MATCH_NORMALIZE_CODA MatchOption = C.KIWI_MATCH_NORMALIZE_CODA
46+
KIWI_MATCH_ALL_WITH_NORMALIZING MatchOption = C.KIWI_MATCH_ALL_WITH_NORMALIZING
4747
)
4848

49+
// Dialect represents a dialect in the Kiwi API.
50+
type Dialect int
51+
52+
const (
53+
DialectStandard Dialect = 0 // KIWI_DIALECT_STANDARD
54+
DialectGyeonggi Dialect = 1 << 0
55+
DialectChungcheong Dialect = 1 << 1
56+
DialectGangwon Dialect = 1 << 2
57+
DialectGyeongsang Dialect = 1 << 3
58+
DialectJeolla Dialect = 1 << 4
59+
DialectJeju Dialect = 1 << 5
60+
DialectHwanghae Dialect = 1 << 6
61+
DialectHamgyeong Dialect = 1 << 7
62+
DialectPyeongan Dialect = 1 << 8
63+
DialectArchaic Dialect = 1 << 9
64+
DialectAll Dialect = (1<<9)*2 - 1
65+
)
66+
67+
const (
68+
// Default values derived from Kiwi C-API defaults (include/kiwi/capi.h).
69+
// For detailed information on these parameters, refer to:
70+
// https://github.com/bab2min/Kiwi/blob/main/include/kiwi/capi.h
71+
DefaultDialectCost float32 = 3.0 // Default penalty for dialect words (dialect_cost)
72+
DefaultTypoThreshold float32 = 2.5 // Default cost threshold for typo correction (typo_threshold)
73+
DefaultNumThread int = 0 // Default number of threads (0 means auto-detect based on CPU cores)
74+
DefaultTopN int = 1 // Default number of results to return from Analyze
75+
)
76+
77+
// Option represents a configuration function for Kiwi initialization.
78+
type Option func(*kiwiOptions)
79+
80+
type kiwiOptions struct {
81+
buildOptions BuildOption
82+
dialects Dialect
83+
numThread int
84+
}
85+
86+
// WithBuildOption sets the BuildOption for initialization.
87+
func WithBuildOption(options BuildOption) Option {
88+
return func(opts *kiwiOptions) {
89+
opts.buildOptions = options
90+
}
91+
}
92+
93+
// WithDialect sets the allowed dialects for initialization.
94+
func WithDialect(dialects Dialect) Option {
95+
return func(opts *kiwiOptions) {
96+
opts.dialects = dialects
97+
}
98+
}
99+
100+
// WithNumThread sets the number of threads for initialization.
101+
// A value of 0 tells Kiwi to automatically use all available CPU cores.
102+
func WithNumThread(threads int) Option {
103+
return func(opts *kiwiOptions) {
104+
opts.numThread = threads
105+
}
106+
}
107+
108+
// AnalyzeOptionFunc represents a configuration function for Analyze.
109+
type AnalyzeOptionFunc func(*AnalyzeOptions)
110+
111+
// WithMatchOption sets the MatchOption for Analyze.
112+
func WithMatchOption(options MatchOption) AnalyzeOptionFunc {
113+
return func(opts *AnalyzeOptions) {
114+
opts.MatchOptions = options
115+
}
116+
}
117+
118+
// WithDialectCost sets the dialect cost for Analyze.
119+
func WithDialectCost(cost float32) AnalyzeOptionFunc {
120+
return func(opts *AnalyzeOptions) {
121+
opts.DialectCost = cost
122+
}
123+
}
124+
125+
// WithTypoThreshold sets the typo threshold for Analyze.
126+
func WithTypoThreshold(threshold float32) AnalyzeOptionFunc {
127+
return func(opts *AnalyzeOptions) {
128+
opts.TypoThreshold = threshold
129+
}
130+
}
131+
132+
// WithTopN sets the maximum number of results to return from Analyze.
133+
func WithTopN(n int) AnalyzeOptionFunc {
134+
return func(opts *AnalyzeOptions) {
135+
opts.TopN = n
136+
}
137+
}
138+
139+
// AnalyzeOptions provides configuration for the Analyze function.
140+
type AnalyzeOptions struct {
141+
MatchOptions MatchOption
142+
DialectCost float32
143+
TypoThreshold float32
144+
TopN int
145+
}
146+
147+
// DefaultAnalyzeOptions returns the default AnalyzeOptions recommended by Kiwi.
148+
func DefaultAnalyzeOptions() AnalyzeOptions {
149+
return AnalyzeOptions{
150+
MatchOptions: KIWI_MATCH_ALL,
151+
DialectCost: DefaultDialectCost,
152+
TypoThreshold: DefaultTypoThreshold,
153+
TopN: DefaultTopN,
154+
}
155+
}
156+
49157
// KiwiVersion returns the version of the kiwi library.
50158
func KiwiVersion() string {
51159
return C.GoString(C.kiwi_version())
@@ -68,9 +176,18 @@ type Kiwi struct {
68176

69177
// New returns a new Kiwi instance.
70178
// Don't forget to call Close after this.
71-
func New(modelPath string, numThread int, options BuildOption) *Kiwi {
179+
func New(modelPath string, opts ...Option) *Kiwi {
180+
options := kiwiOptions{
181+
buildOptions: KIWI_BUILD_DEFAULT,
182+
dialects: DialectStandard,
183+
numThread: DefaultNumThread,
184+
}
185+
for _, opt := range opts {
186+
opt(&options)
187+
}
188+
72189
return &Kiwi{
73-
handler: C.kiwi_init(C.CString(modelPath), C.int(numThread), C.int(options)),
190+
handler: C.kiwi_init(C.CString(modelPath), C.int(options.numThread), C.int(options.buildOptions), C.int(options.dialects)),
74191
}
75192
}
76193

@@ -93,16 +210,26 @@ type TokenResult struct {
93210
}
94211

95212
// Analyze returns the result of the analysis.
96-
func (k *Kiwi) Analyze(text string, topN int, options AnalyzeOption) ([]TokenResult, error) {
213+
func (k *Kiwi) Analyze(text string, opts ...AnalyzeOptionFunc) ([]TokenResult, error) {
97214
var (
98-
blocklist C.kiwi_morphset_h
99215
pretokenized C.kiwi_pretokenized_h
100216
cText = C.CString(text)
101217
)
102218

219+
options := DefaultAnalyzeOptions()
220+
for _, opt := range opts {
221+
opt(&options)
222+
}
223+
103224
defer C.free(unsafe.Pointer(cText))
104225

105-
kiwiResH := C.kiwi_analyze(k.handler, cText, C.int(topN), C.int(options), blocklist, pretokenized)
226+
cOptions := C.kiwi_analyze_option_t{
227+
match_options: C.int(options.MatchOptions),
228+
dialect_cost: C.float(options.DialectCost),
229+
typo_threshold: C.float(options.TypoThreshold),
230+
}
231+
232+
kiwiResH := C.kiwi_analyze(k.handler, cText, C.int(options.TopN), cOptions, pretokenized)
106233
if kiwiResH == nil {
107234
return nil, fmt.Errorf("failed to analyze text")
108235
}
@@ -152,7 +279,7 @@ type SplitResult struct {
152279
}
153280

154281
// SplitSentence returns the line of sentences.
155-
func (k *Kiwi) SplitSentence(text string, options AnalyzeOption) ([]SplitResult, error) {
282+
func (k *Kiwi) SplitSentence(text string, options MatchOption) ([]SplitResult, error) {
156283
cText := C.CString(text)
157284
defer C.free(unsafe.Pointer(cText))
158285

@@ -207,9 +334,18 @@ type KiwiBuilder struct {
207334

208335
// NewBuilder returns a new KiwiBuilder instance.
209336
// Don't forget to call Close after this.
210-
func NewBuilder(modelPath string, numThread int, options BuildOption) *KiwiBuilder {
337+
func NewBuilder(modelPath string, opts ...Option) *KiwiBuilder {
338+
options := kiwiOptions{
339+
buildOptions: KIWI_BUILD_DEFAULT,
340+
dialects: DialectStandard,
341+
numThread: DefaultNumThread,
342+
}
343+
for _, opt := range opts {
344+
opt(&options)
345+
}
346+
211347
return &KiwiBuilder{
212-
handler: C.kiwi_builder_init(C.CString(modelPath), C.int(numThread), C.int(options)),
348+
handler: C.kiwi_builder_init(C.CString(modelPath), C.int(options.numThread), C.int(options.buildOptions), C.int(options.dialects)),
213349
}
214350
}
215351

@@ -292,7 +428,8 @@ func (kb *KiwiBuilder) ExtractWords(readSeeker io.ReadSeeker, minCnt int, maxWor
292428
kb.handler,
293429
C.kiwi_reader_t(C.KiwiReaderBridge),
294430
unsafe.Pointer(h),
295-
C.int(minCnt), C.int(maxWordLen), C.float(minScore), C.float(posThreshold))
431+
C.int(minCnt), C.int(maxWordLen), C.float(minScore), C.float(posThreshold),
432+
)
296433
defer C.kiwi_ws_close(kiwiWsH)
297434

298435
resSize := int(C.kiwi_ws_size(kiwiWsH))

kiwi_example_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import (
77
)
88

99
func Example() {
10-
kb := kiwi.NewBuilder("./base", 1 /*=numThread*/, kiwi.KIWI_BUILD_INTEGRATE_ALLOMORPH /*=options*/)
10+
kb := kiwi.NewBuilder("./base", kiwi.WithNumThread(1), kiwi.WithBuildOption(kiwi.KIWI_BUILD_INTEGRATE_ALLOMORPH))
1111
kb.AddWord("코딩냄비", "NNP", 0)
1212

1313
k := kb.Build()
1414
defer k.Close() // don't forget to Close()!
1515

16-
results, _ := k.Analyze("안녕하세요 코딩냄비입니다. 부글부글.", 1 /*=topN*/, kiwi.KIWI_MATCH_ALL)
16+
results, _ := k.Analyze("안녕하세요 코딩냄비입니다. 부글부글.")
1717

1818
// Print tokens without the score to avoid floating-point output issues
1919
if len(results) > 0 {

0 commit comments

Comments
 (0)