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 |
|---|---|---|
|
|
Validates |
|
|
User selects template and primary object per file |
|
|
Enqueues |
|
|
Fetches template config, builds and sends multimodal request |
|
|
Strips markdown wrapper, extracts JSON, handles table data |
|
|
Maps extracted key-value pairs to Salesforce fields and objects |
Path 2 — Interactive Chat
|
Stage |
Component |
Description |
|---|---|---|
|
|
User picks document and template (which preloads default prompt) |
|
|
Builds multi-turn request including full conversation history |
|
|
Sends request with |
|
|
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 |
|---|---|
|
|
Google Gemini API URL (e.g., |
|
|
Valid Gemini API key |
|
|
Must be |
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 |
|---|---|
|
|
Must be exactly |
|
|
Must be checked |
|
|
Must be populated — this is the instruction sent to the model |
|
|
Must contain valid JSON array of |
|
|
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:
-
Run the above query in Developer Console → Query Editor to confirm active template records exist.
-
If records exist but do not appear, check FLS on
Document_Insight_Template__c— the running user must have Read access onTemplate_Name__c,Primary_Object__c, andSource__c. -
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.
-
For the interactive chat (
geminiChatScreen), templates are filtered toSource__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 |
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:
-
Navigate to Setup → Apex Jobs, find
QueueableCallCustomFormService, and expand the error message. -
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 |
|
|
Malformed JSON body |
|
|
Empty prompt |
|
|
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 |
|
|
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:
-
Verify
Google_API_Key__cin Custom Settings matches the key shown in Google Cloud Console → APIs & Services → Credentials. -
Confirm the
Generative Language APIis enabled in Google Cloud Console → APIs & Services → Enabled APIs. -
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:
-
Review the
Prompt__cfield 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." -
Test the document manually in the Gemini chat (via
geminiChatScreen) to confirm whether Gemini can parse it at all. -
Check the raw response file (if enabled): look for a
ContentVersionattached to thePDF_Insight__crecord — 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
sPossibleKeyvalues defined in the template'sData_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.
Issue: Related Child Records (Table Data) Not Created
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:
-
Check the raw Gemini response — does it contain a
tableDatakey with an array value? -
If not, the prompt does not instruct Gemini to return table data. Update
Prompt__cto explicitly request repeating data in atableDataarray. -
If
tableDatais present in the response but records are not created, verify:-
Related_Object__con the template is a valid object API name -
Relationship_Field__cis a valid lookup/master-detail field API name on the related object pointing to the parent -
Related_Object_Data_Field_Mapping__cis 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 |
|
|
Sample document not found |
|
|
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:
-
Confirm at least one Lazarus
PDF_Insight__cwithStatus = Completedexists for the parent record. -
Check that a
Success*.jsonfile (ContentVersion) is attached to that completed record — this is the raw Lazarus response that the matrix is built from. -
Check for a
Custom_Documents_Event__eplatform event —asyncGetConfidenceMatrixpublishes this event on completion to trigger the UI update. If the event was not published, the@futuremethod 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 |
|---|---|
|
|
LWC controller — feature check, template loading, submission, interactive chat, confidence matrix, template creation |
|
|
Gemini HTTP callout ( |
|
|
Routes per-document processing to Gemini or Lazarus; chains per item |
|
|
Generic field mapping engine — creates/updates SF records from key-value pairs |
|
|
Carries parsed |
|
|
Centralized settings — test mode flag, raw response file toggle, FLS enforcement |
Relevant Objects and Metadata Reference
|
Object / Metadata |
Role |
|---|---|
|
|
Central config — prompt, field mapping JSON, related object config, source |
|
|
Audit record per extraction run; holds status, missing fields, duplicate keys flags |
|
|
Feature flag — gates entire Gemini / Custom Forms UI |
|
|
Carries all context through queueable chain (template name, object, document ID, source) |
|
|
Inner classes for Gemini API structure (GeminiRequest, Content, Part, InlineData, GeminiResponse, Candidate) |