mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
backend: render patient card from searchPatients on a unique match
"Show me <name>'s medical record" relied on the model chaining
searchPatients → getPatient, but Gemini Flash often calls searchPatients
then emits the canned closing line ("Here's the record.") without the
second tool call, so no data-patientCard part is ever written and no card
renders. (The prior Gemini fix only covered the empty-schema list tools.)
searchPatients now writes the record card (data-patientCard, or
data-recordGraph in graph mode) and a source directly when EXACTLY ONE
patient matches — mirroring getPatient — so the common name-lookup flow no
longer depends on a second tool call. Multiple/zero matches keep returning
the disambiguation list. The tool description and system prompt tell the
model the card is already shown on a unique match so it doesn't double-render.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -107,7 +107,10 @@ function systemPrompt(
|
||||
"",
|
||||
"Display tools (read-only):",
|
||||
"- getPatient: when asked about a specific patient by file number / MRN.",
|
||||
"- searchPatients: when given a name; then getPatient on the match.",
|
||||
"- searchPatients: when given a name. If exactly one patient matches it",
|
||||
" already shows that patient's record card — don't call getPatient again,",
|
||||
" just confirm. Only call getPatient yourself for a direct file number / MRN,",
|
||||
" or to pick one of several matches it returns.",
|
||||
"- getPatientLabs: when asked about labs/results/trends.",
|
||||
"- listAppointments: when asked to see the schedule / upcoming visits.",
|
||||
"- listTasks: when asked to see open tasks / to-dos.",
|
||||
|
||||
@@ -189,10 +189,12 @@ export function createChatTools(ctx: ToolContext) {
|
||||
},
|
||||
}),
|
||||
|
||||
// Search the clinic's patients by name or file number.
|
||||
// Search the clinic's patients by name or file number. On a unique match this
|
||||
// also displays that patient's record card directly (see below), so the common
|
||||
// "show me <name>'s record" flow doesn't depend on a follow-up getPatient call.
|
||||
searchPatients: tool({
|
||||
description:
|
||||
"Search the clinic's patients by name fragment. Returns matches with file numbers so you can then call getPatient.",
|
||||
"Search the clinic's patients by name fragment. If EXACTLY ONE patient matches, this already displays their full record card — do NOT call getPatient again; just confirm in one sentence. If multiple match, it returns the matches (with file numbers) so you can disambiguate or call getPatient on the right one.",
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe("Name or file-number fragment to match"),
|
||||
}),
|
||||
@@ -204,17 +206,42 @@ export function createChatTools(ctx: ToolContext) {
|
||||
scopeProviderId,
|
||||
);
|
||||
const q = query.trim().toLowerCase();
|
||||
const matches = all
|
||||
// Keep the full Patient objects so a unique match can render a card.
|
||||
const matched = all
|
||||
.filter(
|
||||
(p) =>
|
||||
p.name.toLowerCase().includes(q) ||
|
||||
p.fileNumber.toLowerCase().includes(q),
|
||||
)
|
||||
.slice(0, 10)
|
||||
.map((p) => {
|
||||
const r = veil.redactPatient(p);
|
||||
return { fileNumber: r.fileNumber, name: r.name, status: p.status };
|
||||
});
|
||||
.slice(0, 10);
|
||||
|
||||
// Unique match → show the record card right here. Gemini often skips the
|
||||
// search→getPatient hand-off and just emits a canned sentence, leaving the
|
||||
// clinician with no card; rendering it directly removes that dependency.
|
||||
if (matched.length === 1) {
|
||||
const patient = matched[0]!;
|
||||
step(`Looking up patient ${patient.fileNumber}`);
|
||||
writer.write(
|
||||
mode === "graph"
|
||||
? { type: "data-recordGraph", data: patient }
|
||||
: { type: "data-patientCard", data: patient },
|
||||
);
|
||||
const sourceId = addSource(
|
||||
`${patient.name} · MRN ${patient.fileNumber}`,
|
||||
"patient",
|
||||
);
|
||||
return {
|
||||
count: 1,
|
||||
shownCard: true,
|
||||
sourceId,
|
||||
patient: forModel(veil.redactPatient(patient)),
|
||||
};
|
||||
}
|
||||
|
||||
const matches = matched.map((p) => {
|
||||
const r = veil.redactPatient(p);
|
||||
return { fileNumber: r.fileNumber, name: r.name, status: p.status };
|
||||
});
|
||||
step(`Found ${matches.length} match(es)`);
|
||||
return { count: matches.length, matches };
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user