vxcontrol/pentagi · error · FatalError

failed to marshal request body: %w

Error message

failed to marshal request body: %w

What it means

Before sending, `search` serializes the CompletionRequest payload with `json.Marshal`. Failure is wrapped as Fatal (backend/pkg/tools/searchers/perplexity.go:168). With a fully in-code struct this is nearly impossible at runtime — it only happens if the payload contains values json cannot encode, such as NaN/Inf floats or a channel/func field added to the struct.

Source

Thrown at backend/pkg/tools/searchers/perplexity.go:168

	}

	// Forming the request
	reqPayload := CompletionRequest{
		Messages:               messages,
		Model:                  p.model(),
		SearchContextSize:      p.contextSize(),
		MaxTokens:              p.maxTokens(),
		Temperature:            p.temperature(),
		TopP:                   p.topP(),
		ReturnImages:           false,
		ReturnRelatedQuestions: false,
		Stream:                 false,
	}

	// Serializing the request
	reqBody, err := json.Marshal(reqPayload)
	if err != nil {
		return "", Fatal(fmt.Errorf("failed to marshal request body: %w", err))
	}

	// Creating HTTP request
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, perplexityURL, bytes.NewBuffer(reqBody))
	if err != nil {
		return "", Fatal(fmt.Errorf("failed to create request: %w", err))
	}

	// Setting request headers
	req.Header.Set("Authorization", "Bearer "+p.apiKey())
	req.Header.Set("Content-Type", "application/json")

	// Sending the request
	resp, err := client.Do(req)
	if err != nil {
		return "", Retryable(fmt.Errorf("failed to send request: %w", err), 0)
	}
	defer resp.Body.Close()

View on GitHub (pinned to ea665308ba)

Solutions

  1. Check the wrapped marshal error — it names the offending field/type
  2. Validate config-derived floats with math.IsNaN/IsInf before building reqPayload and fall back to defaults
  3. Revert recent changes to CompletionRequest; keep only JSON-serializable field types with correct tags
  4. Log reqPayload fields at debug level to spot the bad value

Example fix

// before
temp := parseTemperature(env) // NaN possible
// after
temp := parseTemperature(env)
if math.IsNaN(temp) || math.IsInf(temp, 0) {
    temp = defaultTemperature
}
Defensive patterns

Strategy: validation

Validate before calling

// validate numeric config before building the payload
for _, v := range []float64{temp, topP} {
    if math.IsNaN(v) || math.IsInf(v, 0) {
        return fmt.Errorf("non-finite value in Perplexity config")
    }
}

Try / catch

_, err := engine.Handle(ctx, req)
if err != nil && searchers.IsFatal(err) && strings.Contains(err.Error(), "failed to marshal request body") {
    // inspect logged payload fields for NaN/Inf or non-serializable values
}

Prevention

When it happens

Trigger: `json.Marshal(reqPayload)` errors because a field value is not JSON-encodable — e.g. a NaN/±Inf float leaking into Temperature/TopP from a misparsed config, or a custom build that added an unsupported field type to CompletionRequest.

Common situations: Temperature/TopP env var parsed via strconv/regex producing NaN; custom fork patched CompletionRequest with a non-serializable field; json tag/type mismatch after a struct refactor.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/9b9b1083523f84af. Report an issue: GitHub.