Ctrl K
ComunidadGuías…

internal/gateway/inference_prepare.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package gateway

import (
	"context"
	"strings"

	"github.com/enterpilot/gomodel/internal/core"
	"github.com/enterpilot/gomodel/internal/requestparams"
)

// PrepareChatRequest resolves workflow/model policy and applies translated request patching.
func (o *InferenceOrchestrator) PrepareChatRequest(ctx context.Context, req *core.ChatRequest, meta RequestMeta) (*PreparedChatRequest, error) {
	return prepareTranslated(o, ctx, req, meta, chatPrepareSpec)
}

// PrepareResponsesRequest resolves workflow/model policy and applies translated request patching.
func (o *InferenceOrchestrator) PrepareResponsesRequest(ctx context.Context, req *core.ResponsesRequest, meta RequestMeta) (*PreparedResponsesRequest, error) {
	return prepareTranslated(o, ctx, req, meta, responsesPrepareSpec)
}

// PrepareEmbeddingRequest resolves workflow/model policy for an embeddings request.
func (o *InferenceOrchestrator) PrepareEmbeddingRequest(ctx context.Context, req *core.EmbeddingRequest, meta RequestMeta) (*PreparedEmbeddingRequest, error) {
	if req == nil {
		return nil, core.NewInvalidRequestError("embeddings request is required", nil)
	}
	ctx = WithAttemptRecorder(contextWithRequestID(ctx, meta.RequestID))
	workflow, err := o.ensureTranslatedRequestWorkflow(ctx, meta.Workflow, meta.RequestID, meta.Endpoint, &req.Model, &req.Provider)
	if err != nil {
		return nil, err
	}
	ctx = o.WithCacheRequestContext(ctx, workflow)
	return &PreparedEmbeddingRequest{Context: ctx, Request: req, Workflow: workflow}, nil
}

type translatedPrepareSpec[Req any, Prepared any] struct {
	requiredMessage string
	patchNilMessage string
	selector        func(Req) (*string, *string)
	resolve         func(*InferenceOrchestrator) func(context.Context, Req, *core.Workflow) (context.Context, Req, error)
	patch           func(*InferenceOrchestrator) func(context.Context, Req) (Req, error)
	valid           func(Req) bool
	// overrides applies the primary attempt's forced reasoning effort and
	// request parameters, so the prepared request (and the response cache key
	// built from it) carries what is actually sent upstream. The client's
	// request stays in the context so failover attempts are rebuilt from it
	// with their own overrides.
	overrides func(Req, requestparams.Overrides) (Req, error)
	build     func(context.Context, Req, *core.Workflow) Prepared
}

var chatPrepareSpec = translatedPrepareSpec[*core.ChatRequest, *PreparedChatRequest]{
	requiredMessage: "chat request is required",
	patchNilMessage: "patched chat request is required",
	selector:        chatRequestSelector,
	patch:           chatRequestPatch,
	valid:           validChatRequest,
	overrides:       ChatRequestWithOverrides,
	build: func(ctx context.Context, req *core.ChatRequest, workflow *core.Workflow) *PreparedChatRequest {
		return &PreparedChatRequest{Context: ctx, Request: req, Workflow: workflow}
	},
}

var responsesPrepareSpec = translatedPrepareSpec[*core.ResponsesRequest, *PreparedResponsesRequest]{
	requiredMessage: "responses request is required",
	patchNilMessage: "patched responses request is required",
	selector:        responsesRequestSelector,
	resolve:         responsesHistoryResolve,
	patch:           responsesRequestPatch,
	valid:           validResponsesRequest,
	overrides:       ResponsesRequestWithOverrides,
	build: func(ctx context.Context, req *core.ResponsesRequest, workflow *core.Workflow) *PreparedResponsesRequest {
		return &PreparedResponsesRequest{Context: ctx, Request: req, Workflow: workflow}
	},
}

func prepareTranslated[Req any, Prepared any](
	o *InferenceOrchestrator,
	ctx context.Context,
	req Req,
	meta RequestMeta,
	spec translatedPrepareSpec[Req, Prepared],
) (Prepared, error) {
	var zero Prepared
	if !spec.valid(req) {
		return zero, core.NewInvalidRequestError(spec.requiredMessage, nil)
	}
	ctx = WithAttemptRecorder(ctx)
	model, provider := spec.selector(req)
	var resolve func(context.Context, Req, *core.Workflow) (context.Context, Req, error)
	if spec.resolve != nil {
		resolve = spec.resolve(o)
	}
	ctx, req, workflow, err := prepareTranslatedRequest(o, ctx, req, meta, model, provider, resolve, spec.patch(o), spec.valid, spec.patchNilMessage)
	if err != nil {
		if workflow != nil {
			// A patch-phase error (a guardrail block or short-circuit) still
			// reports the resolved workflow so the caller can render the
			// outcome with routing context.
			return spec.build(ctx, req, workflow), err
		}
		return zero, err
	}
	if spec.overrides != nil {
		if overrides := o.RequestOverrides(ctx, workflow); !overrides.IsZero() {
			ctx = withClientRequest(ctx, req)
			if req, err = spec.overrides(req, overrides); err != nil {
				return zero, overridesError(err)
			}
		}
	}
	return spec.build(ctx, req, workflow), nil
}

func prepareTranslatedRequest[Req any](
	o *InferenceOrchestrator,
	ctx context.Context,
	req Req,
	meta RequestMeta,
	model,
	provider *string,
	resolve func(context.Context, Req, *core.Workflow) (context.Context, Req, error),
	patch func(context.Context, Req) (Req, error),
	valid func(Req) bool,
	patchNilMessage string,
) (context.Context, Req, *core.Workflow, error) {
	ctx = contextWithRequestID(ctx, meta.RequestID)
	workflow, err := o.ensureTranslatedRequestWorkflow(ctx, meta.Workflow, meta.RequestID, meta.Endpoint, model, provider)
	if err != nil {
		var zero Req
		return ctx, zero, nil, err
	}
	ctx = core.WithWorkflow(ctx, workflow)
	if resolve != nil {
		ctx, req, err = resolve(ctx, req, workflow)
		if err != nil {
			var zero Req
			return ctx, zero, workflow, err
		}
	}
	if patch != nil {
		req, err = patch(ctx, req)
		if err != nil {
			var zero Req
			return ctx, zero, workflow, err
		}
		if valid != nil && !valid(req) {
			var zero Req
			return ctx, zero, nil, core.NewInvalidRequestError(patchNilMessage, nil)
		}
	}
	ctx = o.WithCacheRequestContext(ctx, workflow)
	return ctx, req, workflow, nil
}

func validChatRequest(req *core.ChatRequest) bool {
	return req != nil
}

func validResponsesRequest(req *core.ResponsesRequest) bool {
	return req != nil
}

func chatRequestSelector(req *core.ChatRequest) (*string, *string) {
	return &req.Model, &req.Provider
}

func responsesRequestSelector(req *core.ResponsesRequest) (*string, *string) {
	return &req.Model, &req.Provider
}

func chatRequestPatch(o *InferenceOrchestrator) func(context.Context, *core.ChatRequest) (*core.ChatRequest, error) {
	if o.translatedRequestPatcher == nil {
		return nil
	}
	return o.translatedRequestPatcher.PatchChatRequest
}

func responsesRequestPatch(o *InferenceOrchestrator) func(context.Context, *core.ResponsesRequest) (*core.ResponsesRequest, error) {
	if o.translatedRequestPatcher == nil {
		return nil
	}
	return o.translatedRequestPatcher.PatchResponsesRequest
}

// responsesHistoryResolve expands a Responses request's history before the
// prompt phase, telling the resolver every provider type an attempt of the
// request may reach.
func responsesHistoryResolve(o *InferenceOrchestrator) func(context.Context, *core.ResponsesRequest, *core.Workflow) (context.Context, *core.ResponsesRequest, error) {
	if o.responsesHistoryResolver == nil {
		return nil
	}
	return func(ctx context.Context, req *core.ResponsesRequest, workflow *core.Workflow) (context.Context, *core.ResponsesRequest, error) {
		types := []string{o.ProviderTypeForSelector(core.ModelSelector{Model: req.Model, Provider: req.Provider}, ProviderTypeFromWorkflow(workflow))}
		for _, selector := range o.FailoverSelectors(workflow) {
			types = append(types, o.ProviderTypeForSelector(selector, ""))
		}
		return o.responsesHistoryResolver.ResolveResponsesHistory(ctx, req, types)
	}
}

func contextWithRequestID(ctx context.Context, requestID string) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	requestID = strings.TrimSpace(requestID)
	if requestID == "" || strings.TrimSpace(core.GetRequestID(ctx)) == requestID {
		return ctx
	}
	return core.WithRequestID(ctx, requestID)
}

func (o *InferenceOrchestrator) ensureTranslatedRequestWorkflow(
	ctx context.Context,
	current *core.Workflow,
	requestID string,
	endpoint core.EndpointDescriptor,
	model,
	providerHint *string,
) (*core.Workflow, error) {
	if model == nil || providerHint == nil {
		return nil, core.NewInvalidRequestError("model selector targets are required", nil)
	}
	if ctx == nil {
		ctx = context.Background()
	}

	workflow := currentTranslatedWorkflow(current, endpoint)
	var err error
	if workflow == nil {
		resolution, err := ResolveRequestModelWithAuthorizer(ctx, o.provider, o.modelResolver, o.modelAuthorizer, core.NewRequestedModelSelector(*model, *providerHint))
		if err != nil {
			return nil, err
		}
		workflow, err = TranslatedWorkflow(ctx, strings.TrimSpace(requestID), endpoint, resolution, o.workflowPolicyResolver)
		if err != nil {
			return nil, err
		}
		ApplyResolvedSelector(model, providerHint, resolution)
		return workflow, nil
	}

	resolution := workflow.Resolution
	if resolution != nil && o.modelAuthorizer != nil {
		if err := o.modelAuthorizer.ValidateModelAccess(ctx, resolution.ResolvedSelector); err != nil {
			return nil, err
		}
	}
	if resolution == nil {
		resolution, err = ResolveRequestModelWithAuthorizer(ctx, o.provider, o.modelResolver, o.modelAuthorizer, core.NewRequestedModelSelector(*model, *providerHint))
		if err != nil {
			return nil, err
		}
		workflow, err = TranslatedWorkflow(ctx, strings.TrimSpace(requestID), endpoint, resolution, o.workflowPolicyResolver)
		if err != nil {
			return nil, err
		}
	}
	ApplyResolvedSelector(model, providerHint, resolution)
	return workflow, nil
}

func currentTranslatedWorkflow(workflow *core.Workflow, endpoint core.EndpointDescriptor) *core.Workflow {
	if workflow == nil {
		return nil
	}
	if workflow.Mode != core.ExecutionModeTranslated || workflow.Endpoint.Operation != endpoint.Operation {
		return nil
	}
	return workflow
}

// ApplyResolvedSelector updates request model/provider fields to the resolved selector.
func ApplyResolvedSelector(model, providerHint *string, resolution *core.RequestModelResolution) {
	if model == nil || providerHint == nil || resolution == nil {
		return
	}
	*model = resolution.ResolvedSelector.Model
	*providerHint = resolution.ResolvedSelector.Provider
}

// WithCacheRequestContext injects workflow and guardrails cache metadata into ctx.
func (o *InferenceOrchestrator) WithCacheRequestContext(ctx context.Context, workflow *core.Workflow) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	if workflow != nil {
		ctx = core.WithWorkflow(ctx, workflow)
	}
	if workflow != nil && workflow.Policy != nil {
		return core.WithGuardrailsHash(ctx, workflow.GuardrailsHash())
	}
	if o.guardrailsHash != "" {
		return core.WithGuardrailsHash(ctx, o.guardrailsHash)
	}
	return ctx
}