fix(go bindings): unit tests and exported interfaces
This commit is contained in:
parent
221e93a5d7
commit
b751ec1f55
|
|
@ -19,8 +19,12 @@ var (
|
|||
ErrProcessingFailed = errors.New("processing failed")
|
||||
ErrUnsupportedLanguage = errors.New("unsupported language")
|
||||
ErrModelNotMultilingual = errors.New("model is not multilingual")
|
||||
ErrUnableToCreateState = errors.New("unable to create state")
|
||||
ErrModelClosed = errors.Join(errors.New("model has been closed"), ErrInternalAppError)
|
||||
|
||||
// Private errors
|
||||
errParametersRequired = errors.New("parameters are required")
|
||||
errModelRequired = errors.New("model is required")
|
||||
errUnableToCreateState = errors.New("unable to create state")
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -14,11 +14,19 @@ import (
|
|||
type context struct {
|
||||
n int
|
||||
model *model
|
||||
st WhisperState
|
||||
st *whisperState
|
||||
*Parameters
|
||||
}
|
||||
|
||||
func NewContext(model *model, params *Parameters) (*context, error) {
|
||||
if model == nil {
|
||||
return nil, errModelRequired
|
||||
}
|
||||
|
||||
if params == nil {
|
||||
return nil, errParametersRequired
|
||||
}
|
||||
|
||||
c := new(context)
|
||||
c.model = model
|
||||
|
||||
|
|
@ -32,7 +40,7 @@ func NewContext(model *model, params *Parameters) (*context, error) {
|
|||
|
||||
st := ctx.Whisper_init_state()
|
||||
if st == nil {
|
||||
return nil, ErrUnableToCreateState
|
||||
return nil, errUnableToCreateState
|
||||
}
|
||||
|
||||
c.st = newWhisperState(st)
|
||||
|
|
@ -48,7 +56,7 @@ func (context *context) DetectedLanguage() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
st, err := context.st.UnsafeState()
|
||||
st, err := context.st.unsafeState()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -62,7 +70,7 @@ func (context *context) DetectedLanguage() string {
|
|||
|
||||
// Close frees the whisper state and marks the context as closed.
|
||||
func (context *context) Close() error {
|
||||
return context.st.Close()
|
||||
return context.st.close()
|
||||
}
|
||||
|
||||
// Params returns a high-level parameters wrapper
|
||||
|
|
@ -100,7 +108,7 @@ func (context *context) WhisperLangAutoDetect(offset_ms int, n_threads int) ([]f
|
|||
return nil, err
|
||||
}
|
||||
|
||||
st, err := context.st.UnsafeState()
|
||||
st, err := context.st.unsafeState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -135,7 +143,7 @@ func (context *context) Process(
|
|||
return err
|
||||
}
|
||||
|
||||
st, err := context.st.UnsafeState()
|
||||
st, err := context.st.unsafeState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -168,7 +176,7 @@ func (context *context) NextSegment() (Segment, error) {
|
|||
return Segment{}, err
|
||||
}
|
||||
|
||||
st, err := context.st.UnsafeState()
|
||||
st, err := context.st.unsafeState()
|
||||
if err != nil {
|
||||
return Segment{}, err
|
||||
}
|
||||
|
|
@ -190,48 +198,48 @@ func (context *context) IsMultilingual() bool {
|
|||
// Token helpers
|
||||
// Deprecated: Use Model.IsText() instead - token checking is model-specific.
|
||||
func (context *context) IsText(t Token) bool {
|
||||
result, _ := context.model.TokenIdentifier().IsText(t)
|
||||
result, _ := context.model.tokenIdentifier().IsText(t)
|
||||
return result
|
||||
}
|
||||
|
||||
// Deprecated: Use Model.IsBEG() instead - token checking is model-specific.
|
||||
func (context *context) IsBEG(t Token) bool {
|
||||
result, _ := context.model.TokenIdentifier().IsBEG(t)
|
||||
result, _ := context.model.tokenIdentifier().IsBEG(t)
|
||||
return result
|
||||
}
|
||||
|
||||
// Deprecated: Use Model.IsSOT() instead - token checking is model-specific.
|
||||
func (context *context) IsSOT(t Token) bool {
|
||||
result, _ := context.model.TokenIdentifier().IsSOT(t)
|
||||
result, _ := context.model.tokenIdentifier().IsSOT(t)
|
||||
return result
|
||||
}
|
||||
|
||||
// Deprecated: Use Model.IsEOT() instead - token checking is model-specific.
|
||||
func (context *context) IsEOT(t Token) bool {
|
||||
result, _ := context.model.TokenIdentifier().IsEOT(t)
|
||||
result, _ := context.model.tokenIdentifier().IsEOT(t)
|
||||
return result
|
||||
}
|
||||
|
||||
// Deprecated: Use Model.IsPREV() instead - token checking is model-specific.
|
||||
func (context *context) IsPREV(t Token) bool {
|
||||
result, _ := context.model.TokenIdentifier().IsPREV(t)
|
||||
result, _ := context.model.tokenIdentifier().IsPREV(t)
|
||||
return result
|
||||
}
|
||||
|
||||
// Deprecated: Use Model.IsSOLM() instead - token checking is model-specific.
|
||||
func (context *context) IsSOLM(t Token) bool {
|
||||
result, _ := context.model.TokenIdentifier().IsSOLM(t)
|
||||
result, _ := context.model.tokenIdentifier().IsSOLM(t)
|
||||
return result
|
||||
}
|
||||
|
||||
// Deprecated: Use Model.IsNOT() instead - token checking is model-specific.
|
||||
func (context *context) IsNOT(t Token) bool {
|
||||
result, _ := context.model.TokenIdentifier().IsNOT(t)
|
||||
result, _ := context.model.tokenIdentifier().IsNOT(t)
|
||||
return result
|
||||
}
|
||||
|
||||
func (context *context) SetLanguage(lang string) error {
|
||||
if context.model.whisperContext().IsClosed() {
|
||||
if context.model.whisperContext().isClosed() {
|
||||
// TODO: remove this logic after deprecating the ErrInternalAppError
|
||||
return ErrModelClosed
|
||||
}
|
||||
|
|
@ -245,7 +253,7 @@ func (context *context) SetLanguage(lang string) error {
|
|||
|
||||
// Deprecated: Use Model.IsLANG() instead - token checking is model-specific.
|
||||
func (context *context) IsLANG(t Token, lang string) bool {
|
||||
result, _ := context.model.TokenIdentifier().IsLANG(t, lang)
|
||||
result, _ := context.model.tokenIdentifier().IsLANG(t, lang)
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -329,7 +329,11 @@ func TestContext_VAD_And_Diarization_Params_DoNotPanic(t *testing.T) {
|
|||
assert.NoError(err)
|
||||
defer func() { _ = model.Close() }()
|
||||
|
||||
ctx, err := whisper.NewContext(model, nil)
|
||||
params, err := whisper.NewParameters(model, whisper.SAMPLING_GREEDY, nil)
|
||||
assert.NoError(err)
|
||||
assert.NotNil(params)
|
||||
|
||||
ctx, err := whisper.NewContext(model, params)
|
||||
assert.NoError(err)
|
||||
defer func() { _ = ctx.Close() }()
|
||||
|
||||
|
|
|
|||
|
|
@ -20,32 +20,6 @@ type ProgressCallback func(int)
|
|||
// continue processing. It is called during the Process function
|
||||
type EncoderBeginCallback func() bool
|
||||
|
||||
type TokenIdentifier interface {
|
||||
// Test for "begin" token
|
||||
IsBEG(Token) (bool, error)
|
||||
|
||||
// Test for "start of transcription" token
|
||||
IsSOT(Token) (bool, error)
|
||||
|
||||
// Test for "end of transcription" token
|
||||
IsEOT(Token) (bool, error)
|
||||
|
||||
// Test for "start of prev" token
|
||||
IsPREV(Token) (bool, error)
|
||||
|
||||
// Test for "start of lm" token
|
||||
IsSOLM(Token) (bool, error)
|
||||
|
||||
// Test for "no timestamps" token
|
||||
IsNOT(Token) (bool, error)
|
||||
|
||||
// Test for token associated with a specific language
|
||||
IsLANG(Token, string) (bool, error)
|
||||
|
||||
// Test for text token
|
||||
IsText(Token) (bool, error)
|
||||
}
|
||||
|
||||
type ParamsConfigure func(*Parameters)
|
||||
|
||||
// Model is the interface to a whisper model. Create a new model with the
|
||||
|
|
@ -74,65 +48,8 @@ type Model interface {
|
|||
ResetTimings()
|
||||
}
|
||||
|
||||
// // Parameters configures decode / processing behavior
|
||||
// type Parameters interface {
|
||||
// SetTranslate(bool)
|
||||
// SetSplitOnWord(bool)
|
||||
// SetThreads(uint)
|
||||
// SetOffset(time.Duration)
|
||||
// SetDuration(time.Duration)
|
||||
// SetTokenThreshold(float32)
|
||||
// SetTokenSumThreshold(float32)
|
||||
// SetMaxSegmentLength(uint)
|
||||
// SetTokenTimestamps(bool)
|
||||
// SetMaxTokensPerSegment(uint)
|
||||
// SetAudioCtx(uint)
|
||||
// SetMaxContext(n int)
|
||||
// SetBeamSize(n int)
|
||||
// SetEntropyThold(t float32)
|
||||
// SetInitialPrompt(prompt string)
|
||||
|
||||
// SetNoContext(bool)
|
||||
// SetPrintSpecial(bool)
|
||||
// SetPrintProgress(bool)
|
||||
// SetPrintRealtime(bool)
|
||||
// SetPrintTimestamps(bool)
|
||||
|
||||
// // Enable extra debug info (e.g., dump log_mel)
|
||||
// SetDebugMode(bool)
|
||||
// // Diarization (tinydiarize)
|
||||
// SetDiarize(bool)
|
||||
|
||||
// // Voice Activity Detection (VAD)
|
||||
// SetVAD(bool)
|
||||
// SetVADModelPath(string)
|
||||
// SetVADThreshold(float32)
|
||||
// SetVADMinSpeechMs(int)
|
||||
// SetVADMinSilenceMs(int)
|
||||
// SetVADMaxSpeechSec(float32)
|
||||
// SetVADSpeechPadMs(int)
|
||||
// SetVADSamplesOverlap(float32)
|
||||
|
||||
// // Set the temperature
|
||||
// SetTemperature(t float32)
|
||||
|
||||
// // Set the fallback temperature incrementation
|
||||
// // Pass -1.0 to disable this feature
|
||||
// SetTemperatureFallback(t float32)
|
||||
|
||||
// // Set the language
|
||||
// // If the model is not multilingual, this will return an error
|
||||
// SetLanguage(string) error
|
||||
|
||||
// // Set single segment mode
|
||||
// SetSingleSegment(bool)
|
||||
|
||||
// // Getter methods
|
||||
// Language() string
|
||||
// Threads() int
|
||||
// }
|
||||
|
||||
// Context is the speech recognition context.
|
||||
// Deprecated: Use NewContext implementation struct instead of relying on this interface
|
||||
type Context interface {
|
||||
io.Closer
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import (
|
|||
)
|
||||
|
||||
type model struct {
|
||||
path string
|
||||
ctx *whisperCtx
|
||||
tokenIdentifier *tokenIdentifier
|
||||
path string
|
||||
ctx *whisperCtx
|
||||
tokId *tokenIdentifier
|
||||
}
|
||||
|
||||
// Make sure model adheres to the interface
|
||||
|
|
@ -33,7 +33,7 @@ func NewModel(
|
|||
return nil, ErrUnableToLoadModel
|
||||
} else {
|
||||
model.ctx = newWhisperCtx(ctx)
|
||||
model.tokenIdentifier = newTokenIdentifier(model.ctx)
|
||||
model.tokId = newTokenIdentifier(model.ctx)
|
||||
model.path = path
|
||||
}
|
||||
|
||||
|
|
@ -42,20 +42,13 @@ func NewModel(
|
|||
}
|
||||
|
||||
func (model *model) Close() error {
|
||||
return model.ctx.Close()
|
||||
}
|
||||
|
||||
func (model *model) WhisperContext() WhisperContext {
|
||||
return model.ctx
|
||||
return model.ctx.close()
|
||||
}
|
||||
|
||||
func (model *model) whisperContext() *whisperCtx {
|
||||
return model.ctx
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// STRINGIFY
|
||||
|
||||
func (model *model) String() string {
|
||||
str := "<whisper.model"
|
||||
if model.ctx != nil {
|
||||
|
|
@ -65,9 +58,6 @@ func (model *model) String() string {
|
|||
return str + ">"
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC METHODS
|
||||
|
||||
// Return true if model is multilingual (language and translation options are supported)
|
||||
func (model *model) IsMultilingual() bool {
|
||||
ctx, err := model.ctx.unsafeContext()
|
||||
|
|
@ -132,7 +122,6 @@ func (model *model) ResetTimings() {
|
|||
ctx.Whisper_reset_timings()
|
||||
}
|
||||
|
||||
// WhisperContext returns the low-level whisper context, or error if the model is closed.
|
||||
func (model *model) TokenIdentifier() TokenIdentifier {
|
||||
return model.tokenIdentifier
|
||||
func (model *model) tokenIdentifier() *tokenIdentifier {
|
||||
return model.tokId
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,6 @@ package whisper
|
|||
|
||||
import whisper "github.com/ggerganov/whisper.cpp/bindings/go"
|
||||
|
||||
type WhisperContext interface {
|
||||
// Close closes the whisper context
|
||||
Close() error
|
||||
|
||||
// IsClosed returns true if the whisper context is closed
|
||||
IsClosed() bool
|
||||
}
|
||||
|
||||
type whisperCtx struct {
|
||||
ctx *whisper.Context
|
||||
}
|
||||
|
|
@ -20,7 +12,7 @@ func newWhisperCtx(ctx *whisper.Context) *whisperCtx {
|
|||
}
|
||||
}
|
||||
|
||||
func (ctx *whisperCtx) Close() error {
|
||||
func (ctx *whisperCtx) close() error {
|
||||
if ctx.ctx == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -31,16 +23,14 @@ func (ctx *whisperCtx) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ctx *whisperCtx) IsClosed() bool {
|
||||
func (ctx *whisperCtx) isClosed() bool {
|
||||
return ctx.ctx == nil
|
||||
}
|
||||
|
||||
func (ctx *whisperCtx) unsafeContext() (*whisper.Context, error) {
|
||||
if ctx.IsClosed() {
|
||||
if ctx.isClosed() {
|
||||
return nil, ErrModelClosed
|
||||
}
|
||||
|
||||
return ctx.ctx, nil
|
||||
}
|
||||
|
||||
var _ WhisperContext = (*whisperCtx)(nil)
|
||||
|
|
|
|||
|
|
@ -14,15 +14,15 @@ const testModelPathCtx = "../../models/ggml-small.en.bin"
|
|||
func TestWhisperCtx_NilWrapper(t *testing.T) {
|
||||
wctx := newWhisperCtx(nil)
|
||||
|
||||
assert.True(t, wctx.IsClosed())
|
||||
assert.True(t, wctx.isClosed())
|
||||
|
||||
raw, err := wctx.unsafeContext()
|
||||
assert.Nil(t, raw)
|
||||
require.ErrorIs(t, err, ErrModelClosed)
|
||||
|
||||
require.NoError(t, wctx.Close())
|
||||
require.NoError(t, wctx.close())
|
||||
// idempotent
|
||||
require.NoError(t, wctx.Close())
|
||||
require.NoError(t, wctx.close())
|
||||
}
|
||||
|
||||
func TestWhisperCtx_Lifecycle(t *testing.T) {
|
||||
|
|
@ -34,22 +34,22 @@ func TestWhisperCtx_Lifecycle(t *testing.T) {
|
|||
require.NotNil(t, raw)
|
||||
|
||||
wctx := newWhisperCtx(raw)
|
||||
assert.False(t, wctx.IsClosed())
|
||||
assert.False(t, wctx.isClosed())
|
||||
|
||||
got, err := wctx.unsafeContext()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
|
||||
// close frees underlying ctx and marks closed
|
||||
require.NoError(t, wctx.Close())
|
||||
assert.True(t, wctx.IsClosed())
|
||||
require.NoError(t, wctx.close())
|
||||
assert.True(t, wctx.isClosed())
|
||||
|
||||
got, err = wctx.unsafeContext()
|
||||
assert.Nil(t, got)
|
||||
require.ErrorIs(t, err, ErrModelClosed)
|
||||
|
||||
// idempotent
|
||||
require.NoError(t, wctx.Close())
|
||||
require.NoError(t, wctx.close())
|
||||
// no further free; raw already freed by wctx.Close()
|
||||
}
|
||||
|
||||
|
|
@ -75,11 +75,11 @@ func TestWhisperCtx_FromModelLifecycle(t *testing.T) {
|
|||
// Close model should close underlying context
|
||||
require.NoError(t, model.Close())
|
||||
|
||||
assert.True(t, wc.IsClosed())
|
||||
assert.True(t, wc.isClosed())
|
||||
raw, err = wc.unsafeContext()
|
||||
assert.Nil(t, raw)
|
||||
require.ErrorIs(t, err, ErrModelClosed)
|
||||
|
||||
// Idempotent close on wrapper
|
||||
require.NoError(t, wc.Close())
|
||||
require.NoError(t, wc.close())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,22 +2,17 @@ package whisper
|
|||
|
||||
import whisper "github.com/ggerganov/whisper.cpp/bindings/go"
|
||||
|
||||
type WhisperState interface {
|
||||
Close() error
|
||||
UnsafeState() (*whisper.State, error)
|
||||
}
|
||||
|
||||
type whisperState struct {
|
||||
state *whisper.State
|
||||
}
|
||||
|
||||
func newWhisperState(state *whisper.State) WhisperState {
|
||||
func newWhisperState(state *whisper.State) *whisperState {
|
||||
return &whisperState{
|
||||
state: state,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *whisperState) Close() error {
|
||||
func (s *whisperState) close() error {
|
||||
if s.state == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -28,7 +23,7 @@ func (s *whisperState) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *whisperState) UnsafeState() (*whisper.State, error) {
|
||||
func (s *whisperState) unsafeState() (*whisper.State, error) {
|
||||
if s.state == nil {
|
||||
return nil, ErrModelClosed
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ const testModelPathState = "../../models/ggml-small.en.bin"
|
|||
func TestWhisperState_NilWrapper(t *testing.T) {
|
||||
ws := newWhisperState(nil)
|
||||
|
||||
state, err := ws.UnsafeState()
|
||||
state, err := ws.unsafeState()
|
||||
assert.Nil(t, state)
|
||||
require.ErrorIs(t, err, ErrModelClosed)
|
||||
|
||||
require.NoError(t, ws.Close())
|
||||
require.NoError(t, ws.close())
|
||||
// idempotent
|
||||
require.NoError(t, ws.Close())
|
||||
require.NoError(t, ws.close())
|
||||
}
|
||||
|
||||
func TestWhisperState_Lifecycle(t *testing.T) {
|
||||
|
|
@ -37,17 +37,17 @@ func TestWhisperState_Lifecycle(t *testing.T) {
|
|||
|
||||
ws := newWhisperState(state)
|
||||
|
||||
got, err := ws.UnsafeState()
|
||||
got, err := ws.unsafeState()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, got)
|
||||
|
||||
// close frees underlying state and marks closed
|
||||
require.NoError(t, ws.Close())
|
||||
require.NoError(t, ws.close())
|
||||
|
||||
got, err = ws.UnsafeState()
|
||||
got, err = ws.unsafeState()
|
||||
assert.Nil(t, got)
|
||||
require.ErrorIs(t, err, ErrModelClosed)
|
||||
|
||||
// idempotent
|
||||
require.NoError(t, ws.Close())
|
||||
require.NoError(t, ws.close())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue