CoolFace
Modelpublic

erwingm10/gemma4-e2b-patrimo-v3.3

sourceHugging Faceapache-2.0updated 5mo agoView on Hugging Face
0likes
Model Card

Patrimo Gemma 4 E2B — Fine-tuned (v3.3)

Model: patrimo-ai/gemma4-e2b-patrimo-v3.3 Base: `google/gemma-4-E2B-it` (2B params, multimodal) Method: QLoRA r=64, lora_alpha=128, 4-bit quantization Training data: 2,092 examples (chat + auto-tracker + phase-1 pre-screener)

LoRA fine-tune de Gemma 4 E2B especializado en finanzas personales conversacional + clasificación de notificaciones bancarias para la app Patrimo (Android).

Casos de uso

  1. 1.Chat IA financiero — registro de gastos/ingresos via tool calling, consultas de balance, reportes, cierre de mes
  2. 2.Auto-tracker de notificaciones — clasificación de notificaciones bancarias en APPROVED/DISCARDED/REVIEW con extracción de monto, merchant, categoría
  3. 3.Pre-screener Phase 1 — extracción rápida de locale + monto antes de clasificación completa

Optimizado para correr on-device en Android via LiteRT-LM SDK (también disponible en GGUF para llama.cpp).

Métricas (benchmark v3, n=208 cases)

Auto-Tracker (notificaciones bancarias)

MétricaScoreMínimo target
Phase 1 accuracy100% ✅95%
Phase 1 financial F1100% ✅95%
AT jsonvalidrate100% ✅98%
AT amount_accuracy100% ✅90%
AT merchant_clean98% ✅85%
AT verdict_accuracy70.8%88%
AT BILL_REMINDER100% ✅-
AT DUPLICATE100% ✅-
AT OWNACCOUNTTRANSFER100% ✅-
AT MARKETING100% ✅-
AT OTPORAUTH100% ✅-

Chat IA (tool calling)

MétricaScoreMínimo target
Chat refusal_accuracy92.3% ✅85%
Chat checkduplicaterate66.7%95%
Chat tool_accuracy48.8%85%
Chat jsonvalidrate54.4%98%

Nota sobre métricas chat: Con system prompt completo de producción (verbose), las métricas chat suben significativamente. El benchmark usa system prompt minimal para evaluar peor caso. En producción Android usando OnDeviceSystemPrompt.kt, esperar +20-30% en checkduplicaterate.

Quickstart

Inferencia con Transformers + Unsloth

python
from unsloth import FastModel
from unsloth.chat_templates import get_chat_template

model, tokenizer = FastModel.from_pretrained(
    model_name="patrimo-ai/gemma4-e2b-patrimo-v3.3",
    max_seq_length=1024,
    load_in_4bit=True,
)
tokenizer = get_chat_template(tokenizer, chat_template="gemma-4")
FastModel.for_inference(model)

# Auto-tracker example
messages = [
    {
        "role": "system",
        "content": "Eres el revisor de transacciones de Patrimo. Responde con UNA SOLA LINEA de JSON valido.\n\nSCHEMA:\n(A) APPROVED: {\"verdict\":\"APPROVED\",\"amount\":NUM,\"type\":\"income\"|\"expense\",\"merchant\":\"STR\",\"category\":\"STR\",\"confidence\":0.0-1.0,\"reasoning\":\"1 linea\"}\n(B) DISCARDED: {\"verdict\":\"DISCARDED\",\"discard_reason_code\":\"CODE\",\"discard_reason_text\":\"STR\",\"reasoning\":\"1 linea\"}\n(C) REVIEW: {\"verdict\":\"REVIEW\",...}",
    },
    {
        "role": "user",
        "content": '<NOTIF>Titulo:"Bancolombia" Cuerpo:"Compra aprobada por COP 45900 en STARBUCKS CHAPINERO" App:co.com.bancolombia.personas.superapp Fecha:2026-05-07</NOTIF>\n\nClasifica.',
    },
]

inputs = tokenizer.apply_chat_template(
    messages, tokenize=True, add_generation_prompt=True, return_tensors="pt"
).to("cuda")
outputs = model.generate(input_ids=inputs, max_new_tokens=200, do_sample=False)
print(tokenizer.decode(outputs[0][inputs.shape[1]:], skip_special_tokens=True))
# Expected: {"verdict":"APPROVED","amount":45900,"type":"expense","merchant":"Starbucks Chapineo","category":"Restaurantes","confidence":0.95,"reasoning":"Compra clara en cafeteria"}

Inferencia con llama.cpp (GGUF)

bash
# Download GGUF Q4_K_M
huggingface-cli download patrimo-ai/gemma4-e2b-patrimo-v3.3 \
    --include "*.gguf" --local-dir ./model

# Run with llama.cpp
./llama-cli -m ./model/gemma4-e2b-patrimo-q4_k_m.gguf \
    -p "<start_of_turn>user\n<NOTIF>...</NOTIF><end_of_turn>\n<start_of_turn>model\n" \
    -n 200 --temp 0

On-device Android (LiteRT-LM)

Convertir GGUF → .litertlm usando los tools oficiales de Google LiteRT-LM, luego cargar via:

kotlin
import com.google.ai.edge.litertlm.Engine
import com.google.ai.edge.litertlm.EngineConfig

val config = EngineConfig(
    modelPath = "/path/to/gemma4-e2b-patrimo-v3.3.litertlm",
    backend = Backend.GPU(),
)
val engine = Engine(config)
engine.initialize()

System prompts de producción

Para máxima precisión, usar los system prompts completos de producción (incluidos en prompts/):

  • —`prompts/auto_tracker_phase1.txt` — pre-screener locale+amount (Phase 1)
  • —`prompts/auto_tracker_phase2.txt` — clasificación completa (Phase 2)
  • —`prompts/chat_ia.txt` — chat con tools

Los prompts incluyen reglas anti-hallucination y few-shot examples que el modelo fue entrenado para seguir.

Schema de tools (Chat IA)

Total: 33 tools. Subset crítico:

typescript
check_duplicate(type: "income"|"expense", amount: number, date: string)
add_expense(amount, date, description, category, payment_method?, provider?, notes?)
add_income(gross_amount, date, description, category, source?, notes?)
list_expenses(period?)
list_income(period?)
get_balance_sheet()
generate_report(period?, type?)
get_budget_status(period?)
add_asset(name, asset_type, current_value, liquidity)
update_asset(asset_id, current_value)
add_liability(name, liability_type, current_balance, creditor)
close_month(period)
add_adjustment(period, type, amount, description, reason, asset_id?, liability_id?)

Schema Auto-Tracker

typescript
type Verdict = "APPROVED" | "DISCARDED" | "REVIEW"

type Approved = {
  verdict: "APPROVED"
  amount: number
  type: "income" | "expense"
  merchant: string
  category: string
  confidence: number  // 0.0-1.0
  reasoning: string
  // Opcionales:
  amount_inferred?: boolean
  amount_inference_reason?: string
  confidence_amount?: number
  currency_original?: string  // "USD"|"EUR"|"MXN"|...
  amount_original?: number
  category_uncertain?: boolean
  detected_account_mask?: string  // "**XXXX"
}

type Discarded = {
  verdict: "DISCARDED"
  discard_reason_code: "OTP_OR_AUTH" | "MARKETING" | "LOGIN_ALERT" |
                      "BILL_REMINDER" | "OWN_ACCOUNT_TRANSFER" |
                      "DUPLICATE" | "REFUND_OR_REVERSAL" | "OTHER_NON_FINANCIAL"
  discard_reason_text: string
  reasoning: string
}

type Review = {
  verdict: "REVIEW"
  suggested_type: "income" | "expense"
  suggested_merchant: string
  suggested_category: string
  value_uncertainty_reason: string
  reasoning: string
}

Schema Phase 1 Pre-screener

typescript
type Phase1 = {
  f: 0 | 1                    // financial flag
  loc?: string                // locale (es-CO, en-US, ...)
  amt?: number                // amount
  cur?: string                // currency (COP, USD, EUR, ...)
  t?: "income" | "expense"    // type hint
}

Categorías soportadas

Gastos: Alimentacion, Transporte, Vivienda, Salud, Entretenimiento, Educacion, Ropa, Tecnologia, Restaurantes, Mercado, Gasolina, Farmacia, Otros

Ingresos: Salario, Freelance, Arriendo, Dividendos, Venta, Otros_ingresos

Locales soportados (Phase 1)

  • —es-CO, es-MX, es-AR, es-PE, es-CL (LATAM Spanish)
  • —pt-BR (Brazilian Portuguese)
  • —en-US, en-CA, en-GB, en-AU, en-IN (English variants)
  • —eu (Eurozone)

Limitaciones conocidas

  1. 1.Chat tool selection — tool_accuracy 48.8% significa que ~50% de los registros chat (registra/anota gasto) van directo a add_expense saltándose check_duplicate. Mitigación en producción: Android puede agregar fallback en OnDeviceAgentLoop para inyectar checkduplicate antes de add*.
  1. 1.AT verdict_accuracy 70.8% — los failures principales son:
  2. 2.Transferencias entre cuentas propias clasificadas como APPROVED (en lugar de DISCARDED+OWNACCOUNTTRANSFER)
  3. 3.Devoluciones tratadas como income
  4. 4.Mitigación: Android AutoTrackerVerdictParser.kt ya tiene fallback mapping para verdicts inválidos (EXPENSE→APPROVED+type=expense, BILL_REMINDER→DISCARDED+code).
  1. 1.Modelo base limitado — Gemma 4 E2B tiene 2B parámetros. Para casos complejos (multi-step queries, ambigüedad de intent), un modelo más grande (E4B) tendría mejor accuracy.
  1. 1.Schema regression occasional — el modelo a veces emite verdict=OWN_ACCOUNT_TRANSFER en lugar del schema correcto. Aprox 2-3% de los casos AT.

Training data composition

FuenteEjemplos
v1 (base SFT)890
v3 lotes (chat + AT)562
v3.1 (anti-pattern fixes)290
v3.2 (check_duplicate flooding + AT fixes)350
Total2,092

Distribución:

  • —Chat tool calls: 1,177 (56%)
  • —Auto-tracker (phase 1+2+legacy+edge): 857 (41%)
  • —Chat natural (refusal/educación): 58 (3%)

Hyperparameters

  • —LoRA rank: 64
  • —LoRA alpha: 128
  • —LoRA dropout: 0
  • —Target modules: qproj, kproj, vproj, oproj, gateproj, upproj, down_proj
  • —Learning rate: 1.5e-4 (cosine schedule)
  • —Warmup steps: 15
  • —Weight decay: 0.01
  • —Optimizer: adamw_8bit
  • —Batch size: 1, gradientaccumulationsteps=4 (effective batch=4)
  • —Epochs: 4
  • —Max seq length: 1024
  • —Quantization: 4-bit QLoRA (bnb)
  • —Hardware: RTX 4070 12GB (108 min training)
  • —Final training loss: 0.2589

Files

gemma4-e2b-patrimo-v3.3/
├── README.md                    # this file
├── adapter_config.json          # LoRA config
├── adapter_model.safetensors    # LoRA weights (~580 MB)
├── tokenizer_config.json
├── tokenizer.model
├── special_tokens_map.json
├── chat_template.jinja
├── prompts/                     # Production system prompts
│   ├── auto_tracker_phase1.txt
│   ├── auto_tracker_phase2.txt
│   └── chat_ia.txt
└── benchmarks/                  # Benchmark results
    └── v3_3_results.json

Citation

bibtex
@misc{patrimo2026gemma,
  title={Patrimo Gemma 4 E2B Fine-tuned},
  author={Erwin Gomez and Patrimo.AI},
  year={2026},
  publisher={HuggingFace},
  howpublished={\url{https://huggingface.co/patrimo-ai/gemma4-e2b-patrimo-v3.3}},
}

License

Apache 2.0 (heredada del modelo base Gemma 4)

Contact

  • —Project: https://patrimo.app
  • —Issues: https://github.com/patrimo-ai/issues
  • —Email: erwingm10@gmail.com