# VetAI Module — Browser QA Test Report

**Date:** 2026-05-22
**Tester:** Claude Code (Chrome MCP, same session as VetAdmission QA)
**Environment:** `http://localhost/static-purchase/public` · DB `static_test09022026` · Static ERP V6.7
**Build:** Module `VetAI` (order 6), requires VetClinic · tables `vai_settings` + `vai_queries` present, 4 permissions registered
**Account used:** `admin` (Admin#1 role)
**AI config under test:** provider Gemini · model `gemini-2.5-flash` · enabled · API key stored (encrypted)

## Summary

**Result: FUNCTIONAL, but one significant bug.** All 8 flows work and there were zero JavaScript console errors on any page. **However, the headline feature (AI clinical advice) returns truncated, unusable responses** with the currently-configured model.

## Detailed results

| # | Flow | Route | Result |
|---|------|-------|--------|
| 1 | AI Assistant page | `GET /vet-ai/assistant` | PASS — Advisory-only banner, query-type cards, patient fields, clinical details |
| 2 | Submit AI query | `POST /vet-ai/assistant/ask` | PARTIAL — Gemini call succeeded but the response was truncated (see Issue 1) |
| 3 | Query logged to DB | `vai_queries` | PASS — id 1 stored: type, status=success, model, tokens, prompt, response |
| 4 | AI History list | `GET /vet-ai/history` | PASS — DataTable, type + status filters render |
| 5 | History detail modal | `GET /vet-ai/history/{id}` | PASS — Prompt + response sections shown |
| 6 | AI Settings page | `GET /vet-ai/settings` | PASS — Provider, key (masked), model, temperature, tokens, budget, system prompt |
| 7 | Test Connection | `POST /vet-ai/settings/test` | PASS — Real API call: "Connection OK — model replied: OK" |
| 8 | Settings Save | `POST /vet-ai/settings/save` | PASS — Persisted; blank API-key field correctly preserved the existing key |

## Issues found

### Issue 1 — MAJOR: AI responses are truncated mid-sentence

The differential-diagnosis test response was cut off at "...always warrants prompt" — only 39 output tokens / 221 characters stored in `vai_queries.response`.

**Root cause:** `gemini-2.5-flash` is a thinking/reasoning model. Its internal reasoning tokens count against `maxOutputTokens` (configured 1024). The model spent almost the entire budget on "thinking", leaving only ~39 tokens for the visible answer, so the API returned `finishReason: MAX_TOKENS` and the answer is incomplete.

`Modules/VetAI/Services/AiClient.php` -> `gemini()` does not send any `thinkingConfig`, so this affects every query on a Gemini 2.x model.

**Fix options:**
- Add `generationConfig.thinkingConfig.thinkingBudget = 0` to disable thinking on flash models, OR
- Raise `max_tokens` substantially, OR
- Use a non-thinking model — the Settings help text and `VaiSetting::defaultModel()` already recommend `gemini-1.5-flash`, which would not have this bug. The configured `gemini-2.5-flash` is the mismatch.

Additionally, `AiClient` / `VaiAssistantController::ask()` treat any non-empty text as success (`ok => $text !== ''`), so a truncated answer is not flagged. Consider also inspecting `finishReason`.

### Issue 2 — MINOR: token accounting undercounts real usage

`AiClient::gemini()` maps only `candidatesTokenCount` to `tokens_out` and ignores `thoughtsTokenCount`. The displayed "~264 tokens" and, more importantly, the **monthly token-budget guard** in `VaiAssistantController::ask()` undercount real API usage by roughly 5x (actual total for the test query was approximately 1,250 tokens). A configured monthly budget can therefore be silently overshot.

### Issue 3 — MINOR: AI response displayed as raw markdown

`**bold**` headings and `*` bullet markers render literally (unparsed) in both the AI Response panel and the History detail modal. No markdown rendering is applied.

### Issue 4 — COSMETIC: Settings Save button mislabeled

The Settings Save button reads " Saved successfully." on a fresh page load, before any save action is performed. The default label appears to be wrong (should read "Save"). Saving itself works correctly.

## Test data left in the system

- `vai_queries` id 1 — the differential-diagnosis test query (Dog / Labrador, vomiting case).
- `vai_settings` id 1 — unchanged config (provider Gemini, model `gemini-2.5-flash`); a no-op Save was performed during QA, API key preserved.

## Recommendation

Fix Issue 1 before the module is used clinically — at present a veterinarian receives a cut-off differential list. Quickest remediation: switch the configured model to `gemini-1.5-flash`, or add `thinkingConfig.thinkingBudget = 0` in `AiClient::gemini()`. Issues 2-4 can follow.
