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"} }