mirror of
https://github.com/lukaszraczylo/mcp-captcharesolver.git
synced 2026-10-12 00:45:28 +00:00
feat(llm): client interface and fake test double
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
// Package llm defines a provider-agnostic LLM client for vision and audio
|
||||
// transcription, with concrete HTTP implementations per provider.
|
||||
package llm
|
||||
|
||||
import "context"
|
||||
|
||||
// Image is a decoded image to send to a vision model.
|
||||
type Image struct {
|
||||
MediaType string // e.g. "image/png", "image/jpeg"
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Options are per-call overrides. Zero values mean "use the client default".
|
||||
type Options struct {
|
||||
Model string
|
||||
Language string
|
||||
Temperature float64
|
||||
MaxTokens int
|
||||
}
|
||||
|
||||
// Client is the abstraction every provider implements and every handler depends on.
|
||||
type Client interface {
|
||||
// Vision sends a prompt plus images and returns the model's text answer.
|
||||
Vision(ctx context.Context, prompt string, images []Image, opts Options) (string, error)
|
||||
// Transcribe converts audio bytes to text. filename carries the extension
|
||||
// for providers that infer the audio format from it. Returns ErrUnsupported
|
||||
// if the provider has no audio capability.
|
||||
Transcribe(ctx context.Context, audio []byte, filename string, opts Options) (string, error)
|
||||
}
|
||||
|
||||
// ErrUnsupported is returned by Transcribe on providers without audio support.
|
||||
type ErrUnsupported struct{ Provider string }
|
||||
|
||||
func (e ErrUnsupported) Error() string {
|
||||
return "audio transcription unsupported by provider " + e.Provider
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package llm
|
||||
|
||||
import "context"
|
||||
|
||||
// Fake is a deterministic Client for tests.
|
||||
type Fake struct {
|
||||
VisionResp string
|
||||
TranscribeResp string
|
||||
VisionErr error
|
||||
TranscribeErr error
|
||||
VisionCalls []VisionCall
|
||||
TranscribeCalls []TranscribeCall
|
||||
}
|
||||
|
||||
// VisionCall records a Vision invocation.
|
||||
type VisionCall struct {
|
||||
Prompt string
|
||||
Opts Options
|
||||
Images []Image
|
||||
}
|
||||
|
||||
// TranscribeCall records a Transcribe invocation.
|
||||
type TranscribeCall struct {
|
||||
Filename string
|
||||
Opts Options
|
||||
Audio []byte
|
||||
}
|
||||
|
||||
// Vision implements Client.
|
||||
func (f *Fake) Vision(_ context.Context, prompt string, images []Image, opts Options) (string, error) {
|
||||
f.VisionCalls = append(f.VisionCalls, VisionCall{Prompt: prompt, Images: images, Opts: opts})
|
||||
return f.VisionResp, f.VisionErr
|
||||
}
|
||||
|
||||
// Transcribe implements Client.
|
||||
func (f *Fake) Transcribe(_ context.Context, audio []byte, filename string, opts Options) (string, error) {
|
||||
f.TranscribeCalls = append(f.TranscribeCalls, TranscribeCall{Audio: audio, Filename: filename, Opts: opts})
|
||||
return f.TranscribeResp, f.TranscribeErr
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFakeClient(t *testing.T) {
|
||||
f := &Fake{VisionResp: "hello", TranscribeResp: "spoken"}
|
||||
got, err := f.Vision(context.Background(), "p", []Image{{MediaType: "image/png", Data: []byte{1}}}, Options{})
|
||||
if err != nil || got != "hello" {
|
||||
t.Fatalf("vision got %q err %v", got, err)
|
||||
}
|
||||
if len(f.VisionCalls) != 1 || f.VisionCalls[0].Prompt != "p" {
|
||||
t.Fatalf("vision call not recorded: %+v", f.VisionCalls)
|
||||
}
|
||||
tr, err := f.Transcribe(context.Background(), []byte{1}, "audio.mp3", Options{Language: "en"})
|
||||
if err != nil || tr != "spoken" {
|
||||
t.Fatalf("transcribe got %q err %v", tr, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user