Milestone 4: Gemini Engine Module and Configuration
Files
ocr/app/engines/gemini_engine.py (NEW)
ocr/app/config.py
ocr/requirements.txt
docker-compose.yml
docker-compose.staging.yml
docker-compose.prod.yml
Requirements
Create GeminiEngine class (standalone, NOT extending OcrEngine -- different interface: OcrEngine.recognize() accepts image bytes and returns text+confidence; GeminiEngine.extract_maintenance() accepts PDF bytes and returns structured JSON)
GeminiEngine uses Vertex AI SDK: GenerativeModel("gemini-2.5-flash") with generate_content()
GeminiEngine.extract_maintenance(pdf_bytes) validates PDF size BEFORE processing. Reject if raw bytes > 20MB with clear error message including file size. Vertex AI SDK handles base64 encoding internally.
Use response_mime_type="application/json" and response_schema for guaranteed JSON structure
Gemini response schema uses camelCase field names (serviceName, intervalMiles, intervalMonths, details) to match backend API convention
Use prompt from issue #129 for maintenance schedule extraction
GeminiEngine._get_client() wraps Vertex AI client initialization in try/except. Catch all exceptions, log full traceback, raise EngineUnavailableError with message: "Vertex AI authentication failed: {exc}"
Gemini reads GOOGLE_APPLICATION_CREDENTIALS env var (same as Vision API). Vertex AI SDK ADC supports external_account (WIF) credential type -- no code changes needed, environment setup only
Add google-cloud-aiplatform>=1.40.0 to requirements.txt
Add environment variables to all docker-compose files for OCR service
Acceptance Criteria
GeminiEngine.extract_maintenance(pdf_bytes) returns structured JSON with maintenanceSchedule array
Each schedule item has serviceName (required), intervalMiles (nullable), intervalMonths (nullable), details (nullable)
PDFs > 20MB (raw bytes) rejected with clear error message including file size
WIF authentication failure raises EngineUnavailableError with diagnostic message
Configuration reads from environment variables with sensible defaults
All field names in response use camelCase
Tests
Test files: ocr/tests/test_gemini_engine.py (NEW)
Test type: unit (mock Vertex AI SDK)
Scenarios:
Normal: Valid PDF returns structured maintenance schedules with camelCase fields
Edge: PDF with no maintenance content returns empty array
Error: PDF > 20MB rejected with size error message
Error: Vertex AI authentication failure raises EngineUnavailableError with diagnostic info
Error: Gemini API call failure handled gracefully
Relates to #129
## Milestone 4: Gemini Engine Module and Configuration
### Files
- `ocr/app/engines/gemini_engine.py` (NEW)
- `ocr/app/config.py`
- `ocr/requirements.txt`
- `docker-compose.yml`
- `docker-compose.staging.yml`
- `docker-compose.prod.yml`
### Requirements
- Create `GeminiEngine` class (standalone, NOT extending OcrEngine -- different interface: OcrEngine.recognize() accepts image bytes and returns text+confidence; GeminiEngine.extract_maintenance() accepts PDF bytes and returns structured JSON)
- GeminiEngine uses Vertex AI SDK: `GenerativeModel("gemini-2.5-flash")` with `generate_content()`
- `GeminiEngine.extract_maintenance(pdf_bytes)` validates PDF size BEFORE processing. Reject if raw bytes > 20MB with clear error message including file size. Vertex AI SDK handles base64 encoding internally.
- Use `response_mime_type="application/json"` and `response_schema` for guaranteed JSON structure
- Gemini response schema uses camelCase field names (serviceName, intervalMiles, intervalMonths, details) to match backend API convention
- Use prompt from issue #129 for maintenance schedule extraction
- Response schema enforces `maintenanceSchedule[]` with `serviceName` (required), `intervalMiles` (nullable), `intervalMonths` (nullable), `details` (nullable)
- `GeminiEngine._get_client()` wraps Vertex AI client initialization in try/except. Catch all exceptions, log full traceback, raise `EngineUnavailableError` with message: "Vertex AI authentication failed: {exc}"
- Gemini reads `GOOGLE_APPLICATION_CREDENTIALS` env var (same as Vision API). Vertex AI SDK ADC supports `external_account` (WIF) credential type -- no code changes needed, environment setup only
- Add config settings: `VERTEX_AI_PROJECT` (required), `VERTEX_AI_LOCATION` (default: us-central1), `GEMINI_MODEL` (default: gemini-2.5-flash)
- Add `google-cloud-aiplatform>=1.40.0` to requirements.txt
- Add environment variables to all docker-compose files for OCR service
### Acceptance Criteria
- `GeminiEngine.extract_maintenance(pdf_bytes)` returns structured JSON with `maintenanceSchedule` array
- Each schedule item has serviceName (required), intervalMiles (nullable), intervalMonths (nullable), details (nullable)
- PDFs > 20MB (raw bytes) rejected with clear error message including file size
- WIF authentication failure raises `EngineUnavailableError` with diagnostic message
- Configuration reads from environment variables with sensible defaults
- All field names in response use camelCase
### Tests
- **Test files**: `ocr/tests/test_gemini_engine.py` (NEW)
- **Test type**: unit (mock Vertex AI SDK)
- **Scenarios**:
- Normal: Valid PDF returns structured maintenance schedules with camelCase fields
- Edge: PDF with no maintenance content returns empty array
- Error: PDF > 20MB rejected with size error message
- Error: Vertex AI authentication failure raises EngineUnavailableError with diagnostic info
- Error: Gemini API call failure handled gracefully
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Relates to #129
Milestone 4: Gemini Engine Module and Configuration
Files
ocr/app/engines/gemini_engine.py(NEW)ocr/app/config.pyocr/requirements.txtdocker-compose.ymldocker-compose.staging.ymldocker-compose.prod.ymlRequirements
GeminiEngineclass (standalone, NOT extending OcrEngine -- different interface: OcrEngine.recognize() accepts image bytes and returns text+confidence; GeminiEngine.extract_maintenance() accepts PDF bytes and returns structured JSON)GenerativeModel("gemini-2.5-flash")withgenerate_content()GeminiEngine.extract_maintenance(pdf_bytes)validates PDF size BEFORE processing. Reject if raw bytes > 20MB with clear error message including file size. Vertex AI SDK handles base64 encoding internally.response_mime_type="application/json"andresponse_schemafor guaranteed JSON structuremaintenanceSchedule[]withserviceName(required),intervalMiles(nullable),intervalMonths(nullable),details(nullable)GeminiEngine._get_client()wraps Vertex AI client initialization in try/except. Catch all exceptions, log full traceback, raiseEngineUnavailableErrorwith message: "Vertex AI authentication failed: {exc}"GOOGLE_APPLICATION_CREDENTIALSenv var (same as Vision API). Vertex AI SDK ADC supportsexternal_account(WIF) credential type -- no code changes needed, environment setup onlyVERTEX_AI_PROJECT(required),VERTEX_AI_LOCATION(default: us-central1),GEMINI_MODEL(default: gemini-2.5-flash)google-cloud-aiplatform>=1.40.0to requirements.txtAcceptance Criteria
GeminiEngine.extract_maintenance(pdf_bytes)returns structured JSON withmaintenanceSchedulearrayEngineUnavailableErrorwith diagnostic messageTests
ocr/tests/test_gemini_engine.py(NEW)Milestone: Gemini Engine Module and Configuration
Phase: Execution | Agent: Developer | Status: PASS
Summary
Audited existing GeminiEngine implementation (committed under refs #133) against all #142 acceptance criteria. Found one gap and fixed it.
Acceptance Criteria Verification
GenerativeModelwithgenerate_content()response_mime_type="application/json"andresponse_schemaenforcementmaintenanceSchedule[]with serviceName required, nullable fields_get_model()wraps init with traceback logging, raises GeminiUnavailableErrorGOOGLE_APPLICATION_CREDENTIALSenv var for WIF authgoogle-cloud-aiplatform>=1.40.0in requirements.txtGap Fixed
logger.exception()calls for full traceback logging in_get_model()exception handlersTest Results
All 18 Gemini engine tests pass:
Commit
f9a650afeat: add traceback logging and spec-aligned error message to GeminiEngine (refs #142)Verdict: PASS | Next: Close issue or continue with parent #129 milestones