Document Insights

Google Gemini Integration — Troubleshooting Guide

This document covers the Google Gemini integration within Document Insights. Gemini is used for two distinct purposes: batch document extraction (structured data written to Salesforce records) and interactive document chat (conversational Q&A). Both paths are covered here.


Integration Architecture Summary

Path 1 — Batch Extraction

Stage

Component

Description

  1. Feature Check

CustomDocumentInsightController

Validates Feature_Entitlement__c.Custom_Forms__c

  1. Template Selection

parentCustomFormInsightRequest LWC

User selects template and primary object per file

  1. Submission

CustomDocumentInsightController.submitCustomFormRequest

Enqueues QueueableCallCustomFormService

  1. API Callout

WS_DocumentInsight.callGoogleGeminiAPI

Fetches template config, builds and sends multimodal request

  1. Response Parsing

WS_DocumentInsight.getGeminiResponseJSON

Strips markdown wrapper, extracts JSON, handles table data

  1. Record Creation

ProcessCustomFormParser.parseCustomFormJSON

Maps extracted key-value pairs to Salesforce fields and objects

Path 2 — Interactive Chat

Stage

Component

Description

  1. Document + Template Selection

geminiChatScreen LWC

User picks document and template (which preloads default prompt)

  1. Message Submission

CustomDocumentInsightController.analyzeDocument

Builds multi-turn request including full conversation history

  1. API Callout

WS_DocumentInsight.geminiAPI

Sends request with x-goog-api-key header

  1. Response Render

geminiChatScreen

Strips markdown formatting, appends to chat UI

Key Difference from MoneyThumb and Ocrolus

The Gemini integration uses a different feature flag (Custom_Forms__c, not PDF_Insights__c) and does not involve webhooks or batch polling. The entire flow — from callout to record creation — runs within a single Queueable transaction per document.


Prerequisites and Configuration Checklist

Custom Settings

Setting

Expected Value

Gemini_Endpoint__c

Google Gemini API URL (e.g., https://generativelanguage.googleapis.com/...)

Google_API_Key__c

Valid Gemini API key

Feature_Entitlement__c.Custom_Forms__c

Must be true

Remote Site Settings

The Gemini API domain (generativelanguage.googleapis.com or equivalent) must be registered under Setup → Remote Site Settings. Missing this entry causes all Gemini callouts to fail with a System.CalloutException rather than an HTTP error code.

Document_Insight_Template__c Records

All Gemini behaviour is template-driven. For Gemini templates to appear in the UI and function correctly:

Field

Requirement

Source__c

Must be exactly Google Gemini (case-sensitive)

Active__c

Must be checked

Prompt__c

Must be populated — this is the instruction sent to the model

Data_Field_Mapping__c

Must contain valid JSON array of GenericFormConfiguration objects

Primary_Object__c

Must be a valid Salesforce object API name


Issue: Gemini Feature Not Accessible

Symptom: The parentCustomFormInsightRequest component shows an error tile or the submit button is disabled.

Root Cause — Wrong feature flag The Gemini / Custom Forms integration checks Feature_Entitlement__c.Custom_Forms__c, not PDF_Insights__c. These are separate flags. An org that has MoneyThumb/Ocrolus enabled may still have Custom_Forms__c disabled.

Resolution: Navigate to Setup → Custom Settings → Feature Entitlement and enable Custom_Forms__c.


Issue: No Templates Appear in the Submission UI

Symptom: The template dropdown is empty when the user opens parentCustomFormInsightRequest or genericCustomFormInsightRequest.

CustomDocumentInsightController.getAllActiveDocumentTemplates() queries:

SELECT Template_Name__c, Primary_Object__c, Source__c
FROM Document_Insight_Template__c
WHERE Active__c = true

Diagnostic Steps:

  1. Run the above query in Developer Console → Query Editor to confirm active template records exist.

  2. If records exist but do not appear, check FLS on Document_Insight_Template__c — the running user must have Read access on Template_Name__c, Primary_Object__c, and Source__c.

  3. If no records exist, templates have not been created. Use the template mapping screen (accessed via the Document Insights app) to create templates before using the submission form.

  4. For the interactive chat (geminiChatScreen), templates are filtered to Source__c = 'Google Gemini' only. Templates configured for Lazarus will not appear there.


Issue: API Returns 503 (Model Overloaded)

Symptom: Apex logs show Gemini returning an HTTP 503 response with an error similar to:

{
  "error": {
    "code": 503,
    "message": "The model is overloaded. Please try again later.",
    "status": "UNAVAILABLE"
  }
}

Unlike 400 (Bad Request) or 403 (Forbidden) errors, a 503 response indicates that the request reached Gemini successfully, but Google's infrastructure could not process it at that moment due to capacity constraints.

Root Cause

A 503 response is a server-side availability issue and is generally not caused by Salesforce configuration, API credentials, templates, or document content.

  • Global Traffic Spike - The Gemini model being used may be experiencing unusually high demand from users worldwide. When available processing capacity is exhausted, Google returns a 503 response until capacity becomes available.

  • Infrastructure Maintenance or Service Degradation - Google periodically performs maintenance on the hardware clusters that power Gemini. Temporary capacity reductions or service disruptions can result in overload errors.

  • Preview or Experimental Models - Preview, Flash, or experimental Gemini models often operate on more limited infrastructure than generally available (GA) models and may encounter overload conditions more frequently.

  • Large or Complex Requests - Very large documents, extensive prompts, or requests requiring significant reasoning can consume more processing resources. During periods of high demand, these requests are more likely to be rejected with a 503 response.

How to Distinguish Common Gemini API Errors

HTTP Status

Meaning

Responsibility

Recommended Action

400

Bad Request

Client Request

Review request format, file type, prompt, and payload structure.

403

Forbidden

Configuration or Quota

Verify API key, enabled APIs, and quota limits.

429

Rate Limit Exceeded

Client Application

Reduce request frequency or increase available quota.

500

Internal Server Error

Gemini Service

Retry after a short delay.

503

Model Overloaded

Gemini Service

Retry using exponential backoff or wait for service capacity to recover


Diagnostic Steps

1. Verify the Response Body

Review the response body returned by Gemini. A true overload condition typically contains:

{
  "error": {
    "code": 503,
    "status": "UNAVAILABLE"
  }
}

If the response instead contains a 403 or 429 status, follow the troubleshooting steps for those errors.

2.Check Google Cloud Status

If multiple users are experiencing failures simultaneously, check Google's service status dashboards for ongoing incidents affecting Gemini services.

3.Verify the Issue Is Intermittent

Submit the same document again after several minutes. If the request succeeds without any configuration changes, the failure was most likely caused by temporary service saturation.


Resolution

  • Retry the Request - Most overload conditions are temporary and resolve automatically within a few minutes. Re-submit the extraction request after a short delay.

  • Implement Exponential Backoff - Avoid immediately retrying failed requests. Repeated retries can contribute to additional congestion.

Recommended retry intervals:

Attempt

Delay

1

1 second

2

2 seconds

3

4 seconds

4

8 seconds

5

16 seconds

Use a Fallback Model

If multiple Gemini models are available within your implementation, consider switching to a different model when a 503 occurs.

Example strategy:

Try Primary Model
        ↓
   HTTP 503?
        ↓
Try Alternate Model
        ↓
   HTTP 503?
        ↓
Wait and Retry

For example:

gemini-1.5-pro
       ↓
gemini-1.5-flash
       ↓
Retry Later

Best Practices

  • Implement retry logic with exponential backoff.

  • Avoid unnecessary duplicate submissions.

  • Monitor API response codes and failure rates.

  • Consider fallback models for production environments.

  • Schedule large batch extraction jobs during off-peak periods when possible.

Important: A 503 "Model Overloaded" response does not indicate an issue with:

  • Google_API_Key__c

  • Remote Site Settings

  • Document_Insight_Template__c configuration

  • Data_Field_Mapping__c

  • Salesforce permissions

The error originates from temporary capacity limitations within Google's Gemini infrastructure and typically resolves without configuration changes.


Issue: File Not Appearing in the Submission Table

Symptom: A file attached to the record does not appear in the parentCustomFormInsightRequest table.

CustomDocumentInsightController.relatedFiles(parentId) queries ContentDocumentLink then filters ContentVersion records to those with ContentSize <= 5242880 (5 MB).

Common Causes:

Cause

Resolution

File exceeds 5 MB

Compress or reduce the file before attaching

File not linked to this record

Ensure the file was uploaded via the record's Files component, not via another record

User lacks access to ContentDocumentLink

Check sharing settings on the file

Note for genericCustomFormInsightRequest: This standalone variant does not read pre-existing files. The user uploads directly via lightning-file-upload, which calls CustomDocumentInsightController.uploadFile() to create a ContentVersion on the fly. File size here is constrained by the lightning-file-upload accept attribute (pdf, png, jpg, jpeg) and the component's max-file-size setting of 8 MB.


Issue: Queueable Job Fails Without Creating Any Records

Symptom: QueueableCallCustomFormService appears in Apex Jobs with status Failed. No PDF_Insight__c is created.

Diagnostic Steps:

  1. Navigate to Setup → Apex Jobs, find QueueableCallCustomFormService, and expand the error message.

  2. Check whether the failure is in routing (before the callout) or within callGoogleGeminiAPI.

Root Cause 1 — Source not set to 'Google Gemini' QueueableCallCustomFormService routes on lstGenericFormWrapper[0].source. If source is null or does not match 'Google Gemini', it falls through to the Lazarus path (callCustomFormService), which will fail because Lazarus endpoints are not configured for Gemini documents.

Verification: Check the source value being set in parentCustomFormInsightRequest.handleSubmitDocument(). It is populated from mapTemplatetoSource[selectedTemplate], which is built from the template's Source__c field. If the template record has a blank or misspelled Source__c, the source will be wrong.

Root Cause 2 — Template not found during callout Inside callGoogleGeminiAPI, a SOQL query fetches the template by Template_Name__c, Primary_Object__c, and Source__c = 'Google Gemini'. If any of these three values do not exactly match the record (including case and whitespace), the query returns zero rows and the method fails before making the API call.


Issue: API Callout Fails (No Response / Connection Error)

Symptom: Debug logs show a System.CalloutException or System.NetException rather than an HTTP status code.

Root Cause — Remote Site Settings missing The Gemini API domain is not registered. Add it under Setup → Remote Site Settings.

Root Cause — Named Credential not configured If the org uses a Named Credential for the Gemini endpoint, verify it is active and the endpoint URL in Gemini_Endpoint__c matches the Named Credential's callout URL format (e.g., callout:GeminiAPI/...).

Root Cause — Test mode active WS_DocumentInsight.geminiAPI() checks ConfigurationService.getTemplateNameForGemini. If this returns a non-null value, the method loads a StaticResource by that name instead of making a live callout. If the named StaticResource does not exist, the method throws a NullPointerException.

Check: Query ConfigurationService settings to see if getTemplateNameForGemini is set. If test mode should not be active in this environment, ensure the value is null or empty.


Issue: API Returns 400 or 403

Symptom: Apex logs show Gemini returning an HTTP 400 or 403 with an error message in the response body.

400 — Bad Request

Likely Cause

Description

Invalid MIME type

HttpHexFormBuilder.resolveMimeType() returned an unsupported type for the file extension. Gemini supports application/pdf, image/png, image/jpeg, image/webp, image/heic, image/heif. Non-supported types cause a 400.

Malformed JSON body

GeminiRequest serialization produced invalid JSON — rare but possible if Content or Part objects contain null fields

Empty prompt

Prompt__c on the template is blank — Gemini rejects requests with no text part

File too large for inline_data

Gemini has a per-request size limit (20 MB for inline data). Files near the 5 MB Salesforce filter limit plus base64 encoding overhead (~33% larger) can approach this.

403 — Forbidden

Likely Cause

Description

Invalid API key

Google_API_Key__c is incorrect, expired, or for the wrong Google Cloud project

API not enabled

The Gemini API is not enabled for the Google Cloud project associated with the key

Quota exceeded

The project has hit its requests-per-minute or tokens-per-day limit

Resolution for 403:

  1. Verify Google_API_Key__c in Custom Settings matches the key shown in Google Cloud Console → APIs & Services → Credentials.

  2. Confirm the Generative Language API is enabled in Google Cloud Console → APIs & Services → Enabled APIs.

  3. Check quota usage in Google Cloud Console → APIs & Services → Quotas.


Issue: API Returns 200 but PDF_Insight__c Shows "Extraction Failed"

Symptom: The Gemini callout succeeds (HTTP 200) but the PDF_Insight__c record is created with Status__c = 'Extraction Failed'.

This status is set inside callGoogleGeminiAPI when geminiAPI() returns a non-200 response. If the status is Extraction Failed despite a 200 response reaching the parser, the failure occurred inside getGeminiResponseJSON.

Root Cause — Gemini returned non-JSON content getGeminiResponseJSON extracts JSON by finding the first { and last } in the response text. If Gemini returns a conversational response ("I'm sorry, I cannot extract data from this document") with no JSON, the method returns a parser with no keyValuePairs, and ProcessCustomFormParser creates a Failed record.

Common triggers:

  • The prompt does not explicitly instruct Gemini to respond in JSON format

  • The document is unreadable (blank pages, corrupted PDF, image-only scanned document with no OCR layer)

  • The document type does not match what the prompt expects (e.g., a bank statement sent to a paystub template)

Resolution:

  1. Review the Prompt__c field on the template. It must explicitly ask for a JSON response. Example instruction to include: "Respond only with a valid JSON object. Do not include any explanation or commentary."

  2. Test the document manually in the Gemini chat (via geminiChatScreen) to confirm whether Gemini can parse it at all.

  3. Check the raw response file (if enabled): look for a ContentVersion attached to the PDF_Insight__c record — it stores the raw Gemini response text for debugging.


Issue: PDF_Insight__c Created but Salesforce Fields Are Blank

Symptom: PDF_Insight__c shows Status = Completed and the primary object record was created, but most or all mapped fields are empty.

The field mapping engine in ProcessCustomFormParser matches keys from Gemini's JSON response against sPossibleKey values in Data_Field_Mapping__c. A mismatch means the extracted value is never written to a field.

Step 1 — Check the Raw Response

If ConfigurationService.rawResponseFile() is enabled, a ContentVersion attached to the PDF_Insight__c record contains the exact JSON Gemini returned. Download it and check:

  • What keys are present in the response?

  • Do they match the sPossibleKey values defined in the template's Data_Field_Mapping__c?

Step 2 — Inspect Data_Field_Mapping__c JSON

The Data_Field_Mapping__c field contains a JSON array of GenericFormConfiguration objects. Each object has a sPossibleKey field that is a tilde (~)-separated list of alternate key names. Example:

[
  {
    "sPossibleKey": "employer_name~Employer Name~employer",
    "sFieldAPIName": "Employer__c",
    "sDataType": "Text",
    "sObjectName": "Income_Insight__c",
    "isParent": false,
    "linkRecord": true
  }
]

If Gemini returns "employerName" but the mapping only lists "employer_name~Employer Name~employer", the value will be silently dropped.

Resolution: Edit the template's Data_Field_Mapping__c to add the key variant Gemini is returning as an additional tilde-separated option.

Step 3 — Check for Duplicate Keys Flag

ProcessCustomFormParser has a bHaltProcessWhenDuplicateFound configuration. If enabled and Gemini returns duplicate keys in its response, processing halts early, leaving later fields blank. Check Duplicate_Keys__c on the PDF_Insight__c record — if populated, duplicate keys caused the halt.

Step 4 — Check Missing_Fields__c

The Missing_Fields__c field on PDF_Insight__c is populated when ProcessCustomFormParser encounters keys it cannot map. Review this field to identify exactly which keys Gemini returned that had no matching configuration.


Symptom: Scalar fields are populated correctly but repeating row data (e.g., transaction line items, earnings entries) is missing.

getGeminiResponseJSON handles tableData as a special case. Gemini must return a tableData array in its JSON response for child records to be created. ProcessCustomFormParser then creates records in relatedObject with fields mapped from Related_Object_Data_Field_Mapping__c.

Diagnostic Steps:

  1. Check the raw Gemini response — does it contain a tableData key with an array value?

  2. If not, the prompt does not instruct Gemini to return table data. Update Prompt__c to explicitly request repeating data in a tableData array.

  3. If tableData is present in the response but records are not created, verify:

    • Related_Object__c on the template is a valid object API name

    • Relationship_Field__c is a valid lookup/master-detail field API name on the related object pointing to the parent

    • Related_Object_Data_Field_Mapping__c is valid JSON with correct field mappings

    • The running user has Create access on the related object


Issue: Wrong Primary Object Record Updated (or New Record Created Instead of Update)

Symptom: Instead of updating the existing record that initiated the request, ProcessCustomFormParser creates a new record of the primary object.

ProcessCustomFormParser updates the existing record only if the current record's object type matches sTemplatePrimaryObject. If there is a mismatch — for example, the template is configured for Account but the request was initiated from a Contact — a new Account record is created rather than updating the Contact.

Resolution: Verify that the Primary_Object__c selected in the submission UI matches the object type of the record initiating the request. The handleDropDownChange() handler in the LWC populates object options from mapTemplates[templateName] — if the template's Primary_Object__c values are configured incorrectly, the dropdown will offer wrong options.

isParent flag behaviour: If isParent = true is set in Data_Field_Mapping__c for a configuration entry, that field's value is written to the parent object (one hop up via lookup) rather than the primary object. Misconfiguring isParent on fields that should write to the primary object will silently skip those fields.


Issue: Interactive Chat Returns No Response or Errors

Symptom: Sending a message in geminiChatScreen produces an error toast or an empty assistant message.

CustomDocumentInsightController.analyzeDocument() returns {isSuccess: false, sMessage: <error>} when the callout fails or the response is unparseable.

Sub-issue: Document not re-sent correctly

Gemini's API is stateless — every turn must include the full document. geminiChatScreen stores the fileData (base64 content) from the initial file selection in chatMessages[] and reconstructs it into every subsequent request. If fileData was not captured correctly on initial file selection, subsequent turns will send the conversation history without the document, causing Gemini to respond that it has no document to reference.

Verification: In the browser console, inspect the chatMessages array after the first message is sent. Each entry should have a non-null fileData property.

Sub-issue: Chat history exceeds token limit

Gemini has a per-request token limit. Long conversations with large documents can exceed this limit, causing a 400 response. analyzeDocument rebuilds the full history on every call — there is no truncation.

Resolution: Start a new chat session. There is currently no built-in history pruning; if this is a recurring issue, the prompt can be restructured to be more concise.

Sub-issue: Template has no prompt preset

Templates in geminiChatScreen are loaded via getGeminiDocumentTemplatesTree(). Template names are formatted as templateName::prompt — the :: separator encodes the default prompt. If a template's Prompt__c is blank, handleSelectTreeTemplate splits on :: and sets an empty promptText, causing the user to send an empty first message to Gemini.


Issue: Template Validation Fails During Template Creation

Symptom: The template mapping screen shows an error when attempting to validate or save a new template.

CustomDocumentInsightController.getJSONfieldMapping() performs a live Gemini callout against the sample document to validate the mapping before saving. This means template creation requires:

  • A valid API key

  • The sample document to be accessible

  • Gemini to return a parseable JSON response for the given prompt

Common failure points:

Failure

Cause

API key error during validation

Google_API_Key__c misconfigured (same as runtime)

Sample document not found

ContentDocumentId passed to validation is inaccessible to the running user

Duplicate key warning

Gemini returned two keys with the same name — template cannot be saved until resolved

No JSON in response

Prompt does not instruct JSON output — edit the prompt before retrying


Issue: Confidence Matrix Not Generating

Symptom: The Confidence Matrix tab in the Document Insights UI shows no data or a spinner that never resolves.

CustomDocumentInsightController.createConfidenceMatrixTable(recordId) checks for an existing ContentVersion with the confidence matrix data. If absent, it calls asyncGetConfidenceMatrix(recordId) — an @future method.

The Confidence Matrix is exclusive to Lazarus (custom OCR) records. It compares Lazarus extraction results against threshold values defined in the template. It does not apply to Gemini-extracted records.

Diagnostic Steps for Lazarus confidence matrix issues:

  1. Confirm at least one Lazarus PDF_Insight__c with Status = Completed exists for the parent record.

  2. Check that a Success*.json file (ContentVersion) is attached to that completed record — this is the raw Lazarus response that the matrix is built from.

  3. Check for a Custom_Documents_Event__e platform event — asyncGetConfidenceMatrix publishes this event on completion to trigger the UI update. If the event was not published, the @future method may have failed silently. Check Apex debug logs for the running user around the time of the call.


Debugging Workflow (Quick Reference)

No PDF_Insight__c created after submission
│
├─ Check: QueueableCallCustomFormService Apex Job status
│   ├─ Failed → Check error; likely template SOQL returned 0 rows or wrong source
│   └─ Completed but no record → callGoogleGeminiAPI threw unhandled exception
│       Check: Apex debug log for the Queueable execution

PDF_Insight__c Status = 'Extraction Failed'
│
└─ Gemini returned non-200 OR getGeminiResponseJSON found no JSON
    Check: Google_API_Key__c valid?
    Check: Remote Site Settings for Gemini domain
    Check: Raw response ContentVersion on record
    Check: Prompt includes JSON response instruction
    Check: Document is text-readable (not a blank scan)

PDF_Insight__c Status = 'Completed' but fields blank
│
├─ Check: Missing_Fields__c — lists keys Gemini returned with no mapping
├─ Check: Duplicate_Keys__c — if set, processing halted early
├─ Check: Raw response ContentVersion — compare keys to Data_Field_Mapping__c sPossibleKey values
└─ Check: isParent flag on configurations — fields may be writing to parent object instead

PDF_Insight__c Status = 'Failed'
│
└─ ProcessCustomFormParser threw an exception
    Check: Primary_Object__c is a valid, accessible object
    Check: sFieldAPIName values in Data_Field_Mapping__c exist on the object
    Check: Running user has Create/Edit access on the target object and fields


Relevant Apex Classes Reference

Class

Responsibility

CustomDocumentInsightController

LWC controller — feature check, template loading, submission, interactive chat, confidence matrix, template creation

WS_DocumentInsight

Gemini HTTP callout (callGoogleGeminiAPI, geminiAPI), request/response construction

QueueableCallCustomFormService

Routes per-document processing to Gemini or Lazarus; chains per item

ProcessCustomFormParser

Generic field mapping engine — creates/updates SF records from key-value pairs

GenericFormInsightParser

Carries parsed keyValuePairs and tableData from response to parser

ConfigurationService

Centralized settings — test mode flag, raw response file toggle, FLS enforcement


Relevant Objects and Metadata Reference

Object / Metadata

Role

Document_Insight_Template__c

Central config — prompt, field mapping JSON, related object config, source

PDF_Insight__c (RecordType: Custom_Forms)

Audit record per extraction run; holds status, missing fields, duplicate keys flags

Feature_Entitlement__c.Custom_Forms__c

Feature flag — gates entire Gemini / Custom Forms UI

GenericFormWrapper

Carries all context through queueable chain (template name, object, document ID, source)

DocumentInsightRequestWrapper

Inner classes for Gemini API structure (GeminiRequest, Content, Part, InlineData, GeminiResponse, Candidate)