(w http.ResponseWriter, r *http.Request)
| 342 | } |
| 343 | |
| 344 | func (s *Server) infer(w http.ResponseWriter, r *http.Request) { |
| 345 | traceID := traceutil.NewTraceID() |
| 346 | spanID := traceutil.NewSpanID() |
| 347 | traceStart := time.Now() |
| 348 | |
| 349 | if r.Method != http.MethodPost { |
| 350 | s.emitInferTrace(traceID, spanID, traceStart, "", "", "", "method_not_allowed") |
| 351 | writeError(w, http.StatusMethodNotAllowed, "", errCodeMethodNotAllowed, "method not allowed") |
| 352 | return |
| 353 | } |
| 354 | |
| 355 | var req types.AIRequest |
| 356 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
| 357 | s.emitInferTrace(traceID, spanID, traceStart, "", "", "", "invalid_json") |
| 358 | writeError(w, http.StatusBadRequest, "", errCodeInvalidRequest, "invalid JSON request body") |
| 359 | return |
| 360 | } |
| 361 | |
| 362 | if strings.TrimSpace(req.TenantID) == "" { |
| 363 | s.emitInferTrace(traceID, spanID, traceStart, "", "", "", "missing_tenant_id") |
| 364 | writeError(w, http.StatusBadRequest, "", errCodeInvalidRequest, "tenant_id is required") |
| 365 | return |
| 366 | } |
| 367 | if strings.TrimSpace(req.TaskType) == "" { |
| 368 | s.emitInferTrace(traceID, spanID, traceStart, "", req.TenantID, "", "missing_task_type") |
| 369 | writeError(w, http.StatusBadRequest, "", errCodeInvalidRequest, "task_type is required") |
| 370 | return |
| 371 | } |
| 372 | if req.Input == nil { |
| 373 | s.emitInferTrace(traceID, spanID, traceStart, "", req.TenantID, "", "missing_input") |
| 374 | writeError(w, http.StatusBadRequest, "", errCodeInvalidRequest, "input is required") |
| 375 | return |
| 376 | } |
| 377 | if req.Options.MaxTokens <= 0 { |
| 378 | s.emitInferTrace(traceID, spanID, traceStart, "", req.TenantID, "", "invalid_max_tokens") |
| 379 | writeError(w, http.StatusBadRequest, "", errCodeInvalidOptions, "options.max_tokens must be > 0") |
| 380 | return |
| 381 | } |
| 382 | |
| 383 | req = types.NormalizeAIRequest(req) |
| 384 | rt := strings.ToLower(strings.TrimSpace(req.RequestType)) |
| 385 | if rt != "" && rt != types.RequestTypeInference && rt != types.RequestTypeRAG && rt != types.RequestTypeAgent { |
| 386 | s.emitInferTrace(traceID, spanID, traceStart, "", req.TenantID, "", "unsupported_request_type") |
| 387 | writeError(w, http.StatusBadRequest, "", errCodeUnsupportedRequestType, "request_type must be inference, rag, or agent") |
| 388 | return |
| 389 | } |
| 390 | |
| 391 | requestID := uuid.NewString() |
| 392 | s.inferReqCounter.Inc() |
| 393 | now := time.Now().UnixMilli() |
| 394 | req.RequestID = requestID |
| 395 | |
| 396 | inferCtx, cancel := context.WithTimeout(r.Context(), s.inferRequestTimeout()) |
| 397 | defer cancel() |
| 398 | |
| 399 | sw := &execution.StepWriter{Store: s.ledger, RequestID: requestID, TenantID: req.TenantID} |
| 400 | sw.OnStep = func(ctx context.Context, stepType, backend, status string, start time.Time, latencyMs int64) { |
| 401 | s.emitInferStepTrace(traceID, requestID, req.TenantID, stepType, backend, status, start, latencyMs) |
nothing calls this directly
no test coverage detected