129 lines
4.4 KiB
Go
129 lines
4.4 KiB
Go
package vision
|
|
|
|
// Assessment is the model's own confidence in reading an image.
|
|
type Assessment string
|
|
|
|
const (
|
|
// AssessmentUnknown means the model reported no self-assessment.
|
|
AssessmentUnknown Assessment = "unknown"
|
|
// AssessmentGood means the model is confident in its read.
|
|
AssessmentGood Assessment = "good"
|
|
// AssessmentPoor means the model judged its recognition as poor.
|
|
AssessmentPoor Assessment = "poor"
|
|
)
|
|
|
|
// ModelInfo describes the main model's vision capability.
|
|
type ModelInfo struct {
|
|
// Name identifies the model.
|
|
Name string
|
|
// SupportsVision reports whether the model accepts image input.
|
|
SupportsVision bool
|
|
// SelfAssessment is the model's own confidence for the current image.
|
|
// It drives the layer-2 tool route when SupportsVision is true.
|
|
SelfAssessment Assessment
|
|
}
|
|
|
|
// DecisionKind is the chosen layered route.
|
|
type DecisionKind string
|
|
|
|
const (
|
|
// KindMain routes the image to the main model with the image in context.
|
|
KindMain DecisionKind = "main"
|
|
// KindVisionTool routes to a dedicated vision tool, returning only the tool
|
|
// result so the image never occupies main-model context.
|
|
KindVisionTool DecisionKind = "vision_tool"
|
|
// KindOCRTool routes to an OCR tool, returning only the extracted text.
|
|
KindOCRTool DecisionKind = "ocr_tool"
|
|
// KindUnsupported means no route can process the image.
|
|
KindUnsupported DecisionKind = "unsupported"
|
|
)
|
|
|
|
// Decision is the outcome of a routing call.
|
|
type Decision struct {
|
|
// Kind is the chosen route.
|
|
Kind DecisionKind
|
|
// Tool names the vision or OCR tool to invoke, set when Kind is a tool route.
|
|
Tool string
|
|
// Reason is a human-readable explanation for observability and logs.
|
|
Reason string
|
|
}
|
|
|
|
// RouteRequest carries routing hints from the caller on top of the model info.
|
|
type RouteRequest struct {
|
|
// TextPrimary reports that the user indicated the image is mainly text, so
|
|
// an OCR tool is preferred over a general vision model.
|
|
TextPrimary bool
|
|
// MainSelfAssessedPoor is set when the main model has already judged its own
|
|
// read of the image as poor, requesting a vision/OCR tool route.
|
|
MainSelfAssessedPoor bool
|
|
}
|
|
|
|
// RouterOption configures a Router.
|
|
type RouterOption func(*Router)
|
|
|
|
// WithVisionTool binds the name of the registered vision-understanding tool.
|
|
func WithVisionTool(name string) RouterOption {
|
|
return func(r *Router) {
|
|
if name != "" {
|
|
r.visionTool = name
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithOCRTool binds the name of the registered OCR tool.
|
|
func WithOCRTool(name string) RouterOption {
|
|
return func(r *Router) {
|
|
if name != "" {
|
|
r.ocrTool = name
|
|
}
|
|
}
|
|
}
|
|
|
|
// Router implements the layered vision routing policy. It does no I/O; the
|
|
// caller uses the returned Decision to dispatch the request.
|
|
type Router struct {
|
|
visionTool string
|
|
ocrTool string
|
|
}
|
|
|
|
// NewRouter builds a Router. Unknown (empty) tool names leave the corresponding
|
|
// route unavailable, so the router degrades to KindMain or KindUnsupported.
|
|
func NewRouter(opts ...RouterOption) *Router {
|
|
r := &Router{}
|
|
for _, o := range opts {
|
|
o(r)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Route picks a layered route for an image request given the target model info
|
|
// and caller hints. The order is: the main model directly when it can see and
|
|
// has not self-assessed poorly (layer 1); otherwise a vision or OCR tool whose
|
|
// result replaces the image in context (layers 2 and 3); otherwise
|
|
// KindUnsupported when nothing can process the image.
|
|
func (r *Router) Route(target ModelInfo, req RouteRequest) Decision {
|
|
poor := target.SelfAssessment == AssessmentPoor || req.MainSelfAssessedPoor
|
|
// Layer 1: the main model accepts images and is confident, so use it first.
|
|
if target.SupportsVision && !poor {
|
|
return Decision{
|
|
Kind: KindMain,
|
|
Reason: "main model supports vision and is confident; use it directly",
|
|
}
|
|
}
|
|
// OCR is preferred when the user says the image is mostly text.
|
|
if req.TextPrimary && r.ocrTool != "" {
|
|
return Decision{Kind: KindOCRTool, Tool: r.ocrTool, Reason: "image is mainly text; route to OCR"}
|
|
}
|
|
if r.visionTool != "" {
|
|
return Decision{Kind: KindVisionTool, Tool: r.visionTool, Reason: "route to dedicated vision tool"}
|
|
}
|
|
if r.ocrTool != "" {
|
|
return Decision{Kind: KindOCRTool, Tool: r.ocrTool, Reason: "route to OCR tool"}
|
|
}
|
|
// No tool is bound. A sighted main model remains the only option.
|
|
if target.SupportsVision {
|
|
return Decision{Kind: KindMain, Reason: "no vision/OCR tool bound; keep image with main model"}
|
|
}
|
|
return Decision{Kind: KindUnsupported, Reason: "main model cannot see and no vision/OCR tool is bound"}
|
|
}
|