Chat with FHIR data: how an LLM reads a patient chart
A language model never reads a chart the way a clinician does. The FHIR resources that make up the chart get fetched, projected into text, and sent to the model as context. Understanding how that works, and where the data actually goes, matters before you point any AI at patient records.
What chatting with FHIR data actually means
An LLM has no native understanding of FHIR. When you ask an agent to look up a patient, what happens is:
- The agent calls a FHIR API (Medplum, in Last EHR's case) and fetches structured resources: Patient, Condition, AllergyIntolerance, Observation, Communication, MedicationRequest, Immunization.
- Those resources are projected into a compact summary: a list of conditions, allergies, medications with dosages, recent vitals and labs, and prior notes.
- That summary, along with your question, goes to the language model as a prompt.
- The model reads the context and responds.
The model itself never touches your FHIR backend. The agent sits in the middle and does the translation. Your backend does not need to know about the model provider, and the model never makes a FHIR API call.
Grounding the model: turning FHIR resources into chart context
Raw FHIR is powerful but verbose. One patient's chart can be dozens of resources, each with codes, references, and metadata a chat does not need. The agent's job is to extract the clinically relevant parts and present them cleanly.
Last EHR's show_patient_info tool does exactly this: it fetches demographics, conditions, allergies, observations (sorted by date), notes, medications, and immunizations, then projects each resource down to the fields that matter (a label, a value, a date). The UI renders those as structured cards; the same projection becomes the model's context.
This grounding step is where ambiguity starts. If an observation is missing a date, or a dosage lives in free text instead of structured fields, the model's context gets weaker. Consistent coding and complete fields in the backend are what make the difference between a model that reflects the chart and one that guesses.
The read path and the write path are different problems
The read path is a privacy question. Whenever the agent opens a chart, that context goes to the model provider with no approval step. This is by design (the agent needs the picture to be useful), but it means your provider choice and agreements govern reads, not any UI control.
The write path is a safety question. When the agent proposes a write, it does not execute. Last EHR shows an approval card with exactly what will be saved, and only your click runs the write. The backend's AccessPolicy then bounds what the signed-in user can actually create. The full mechanics are on the approval-gated writes page, including what the gate does not protect against (approval fatigue, hallucinated content, and the ungated read path).
Try it: the live demo on synthetic patients
The fastest way to understand the flow is the live demo. No sign-up; everything runs on seeded synthetic patients. Try:
- "Find patients named Smith": the agent searches and shows results with a View record button.
- "Show me Jane Smith's chart": the full chart renders as cards: demographics, conditions, medications, observations, immunizations, notes.
- "Record a heart rate of 72 bpm for Maria Garcia": the agent proposes an Observation and stops at the approval card. Nothing saves until you click Approve.
On the public demo, writes are tagged per visitor session, so you only ever see the seed data plus your own edits. That keeps a shared demo from becoming a mess of strangers' writes; it is a demo mechanism, not a product feature.
PHI, HIPAA, and BAAs: where your chart data goes
Last EHR is open-source alpha software, and it is not a HIPAA-covered service. If you point it at real patient data, compliance is on you. The essentials:
- Reads require a provider agreement. Chart context goes to your model provider under your key. Some providers offer BAAs covering API traffic on qualifying plans (OpenAI and Anthropic among them); consumer plans do not qualify.
- The backend must be HIPAA-eligible. Medplum offers HIPAA-eligible hosted plans with BAAs; check their current terms. If you self-host a FHIR server, securing it is your job.
- The layer stores nothing. Last EHR keeps no database and no copy of chart data. Data lives in your backend and, during a request, at your model provider.
- Synthetic data is the right default. The seed script loads four synthetic patients with realistic but fake charts. The live demo uses exactly that data.
The landscape: research, MCP servers, and where Last EHR fits
Last EHR is not the only project connecting language models and FHIR, and it is worth knowing the categories.
Research projects such as LLMonFHIR (an academic open-source effort) study how to ground LLMs on FHIR data: terminology, normalization, evaluation. Useful reading if you want the theory behind context assembly.
MCP servers, including Medplum's own, expose FHIR data to MCP-compatible clients over the Model Context Protocol. That is programmatic plumbing: the right layer if you are building your own agent and want standardized access to a backend. It does not come with a clinician-facing UI or an approval gate.
Last EHR is an application layer: a ready-to-run chat UI with four FHIR tools and the approval card built in. It runs on Medplum today; other FHIR backends are a goal, not yet supported. It is not a replacement for MCP servers or the research; it is what you run when you want a working, permissioned chat over a chart without building one.
Run it on your own FHIR backend
You need a Medplum project (their free tier works for evaluation), a model API key (OpenAI or Anthropic), and the code:
git clone https://github.com/cbetz/last-ehr.git
cd last-ehr
npm install
cp .env.example .env.local # add your Medplum + model keys
npm run seed # load synthetic patients
npm run dev # http://localhost:3000/demoThe FHIR tools live in lib/ai/tools.ts and the agent route in app/api/chat/route.ts. Multi-tenancy, users, and permissions come from your Medplum project (Project, ProjectMembership, AccessPolicy); the layer does not reimplement them. Porting to Aidbox, HAPI, or Firely means adapting the tool implementations; that is the intended seam, and contributions are welcome. Setup details are on the Medplum AI agent page.
Frequently asked questions
When I open a chart in Last EHR, where does the patient data go?
The agent fetches the FHIR resources from your backend (Medplum), projects them into a structured summary, and sends that summary to your configured model provider (OpenAI or Anthropic) as part of the chat prompt, under your API key. The data does not stay in Last EHR; the layer stores no patient data of its own. This is why a BAA with your model provider matters if you ever handle real PHI.
What does the approval gate actually protect against?
It prevents unilateral writes. When the agent proposes adding a note or recording an observation, you see an approval card with the data before it is saved, and you can reject it. It does not prevent chart data from being sent to the model provider on reads; that happens regardless. For privacy you need the right provider agreements and access controls, not just an approval gate.
Can I use Last EHR with real patient data?
Not without proper setup, and not while it is alpha. You would need a BAA with your model provider that covers clinical data, a HIPAA-eligible FHIR backend with its own BAA, and your own security and compliance review. The safe default is synthetic data, which is what the seed script and the live demo use.
Does Last EHR work with backends other than Medplum?
Not yet. The tool implementations use Medplum's client libraries. The architecture (the agent, the tools, the approval gate) is backend-agnostic by design, and Aidbox, HAPI FHIR, and Firely are the goal for future adapters. If you want to port it, the FHIR calls in lib/ai/tools.ts are the seam.
What clinical data can the agent read and write?
Reads: search patients by name, and open a chart showing conditions, allergies, observations (vitals and labs), medications, immunizations, and prior notes. Writes: add a free-text note (a Communication resource) or record an observation (an Observation resource), both approval-gated. Other resource types are not exposed today.
If I self-host, do I still need to think about BAAs?
Yes. Self-hosting addresses infrastructure and data residency, but the agent still sends chart context to your model provider. If that context ever includes real PHI, you need a qualifying agreement with the provider. Self-hosting does not remove that relationship; it just puts you in control of everything else.
Chat with a synthetic chart
Search a patient, open the chart, and watch a proposed write stop at the approval card.