Medplum $ai Operation | Medplum

Overview

Medplum's $ai operation enables integration with large language models (LLMs) through a FHIR-compliant interface. This operation allows you to leverage AI capabilities for conversational interactions and FHIR function calling within your healthcare applications.

The AI operation provides:

Prerequisites

Before using the $ai operation, you need:

  1. Enable AI Feature: The ai feature must be enabled on your Medplum project
  2. Configure OpenAI API Key: Add your OpenAI API key as a project secret named OPENAI_API_KEY in your project settings

Using a LiteLLM Proxy (or other OpenAI-compatible endpoint)

By default, the $ai operation calls OpenAI directly at https://api.openai.com/v1. To route requests through a LiteLLM proxy — or any other OpenAI-compatible API — add an optional project secret named LLM_BASE_URL set to the base URL of the proxy (for example https://litellm.example.com/v1). The operation appends /chat/completions to this value. When LLM_BASE_URL is not set, the operation calls OpenAI as before.

The OPENAI_API_KEY secret is sent as the Authorization: Bearer token to whichever endpoint is configured, so set it to the LiteLLM proxy's key (master key or virtual key) when using a proxy. Because LiteLLM is OpenAI-API-compatible, the request and response formats — including function calling and streaming — are unchanged, and the model parameter accepts any model name your proxy is configured to serve.

Endpoint

POST [baseUrl]/fhir/R4/$ai

Parameters

Input Parameters

Name Type Required Description
messages string Yes JSON string containing the conversation messages array. Each message should have role (user, assistant, or system) and content properties.
model string Yes OpenAI model to use (e.g., gpt-4, gpt-3.5-turbo, gpt-4-turbo)
tools string No JSON string containing the tools array for function calling (optional)

Output Parameters

Name Type Description
content string The AI-generated response text
tool_calls string JSON string containing tool calls array (if the AI decided to call functions)

Basic Usage

Simple Conversation

Here's a basic example without function calling:

curl 'https://api.medplum.com/fhir/R4/$ai' \
  -X POST \
  -H "Content-Type: application/fhir+json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "resourceType": "Parameters",
    "parameter": [\
      {\
        "name": "messages",\
        "valueString": "[{\"role\":\"user\",\"content\":\"What is FHIR?\"}]"\
      },\
      {\
        "name": "model",\
        "valueString": "gpt-4"\
      }\
    ]
  }'

Response:

{
  "resourceType": "Parameters",
  "parameter": [\
    {\
      "name": "content",\
      "valueString": "FHIR (Fast Healthcare Interoperability Resources) is a standard for exchanging healthcare information electronically..."\
    }\
  ]
}

Multi-Turn Conversation

To maintain conversation context, include previous messages:

{
  "resourceType": "Parameters",
  "parameter": [\
    {\
      "name": "messages",\
      "valueString": "[{\"role\":\"user\",\"content\":\"What is FHIR?\"},{\"role\":\"assistant\",\"content\":\"FHIR is a standard...\"},{\"role\":\"user\",\"content\":\"What resources does it define?\"}]"\
    },\
    {\
      "name": "model",\
      "valueString": "gpt-4"\
    }\
  ]
}

FHIR Function Calling

The AI operation supports function calling, enabling AI models to suggest FHIR operations based on user requests.

Defining FHIR Tools

Define a fhir_request tool to allow the AI to interact with FHIR resources:

{
  "type": "function",
  "function": {
    "name": "fhir_request",
    "description": "Make a FHIR request to the Medplum server. Use this to search, read, create, update, or delete FHIR resources.",
    "parameters": {
      "type": "object",
      "properties": {
        "method": {
          "type": "string",
          "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"],
          "description": "HTTP method for the FHIR request"
        },
        "path": {
          "type": "string",
          "description": "FHIR resource path (e.g., 'Patient?name=Smith' or 'Patient/123')"
        },
        "body": {
          "type": "object",
          "description": "FHIR resource to create or update (for POST, PUT, PATCH)"
        }
      },
      "required": ["method", "path"]
    }
  }
}

Example: AI-Suggested Patient Search

Request:

curl 'https://api.medplum.com/fhir/R4/$ai' \
  -X POST \
  -H "Content-Type: application/fhir+json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "resourceType": "Parameters",
    "parameter": [\
      {\
        "name": "messages",\
        "valueString": "[{\"role\":\"user\",\"content\":\"Find patient with phone 718-564-9483\"}]"\
      },\
      {\
        "name": "model",\
        "valueString": "gpt-4"\
      },\
      {\
        "name": "tools",\
        "valueString": "[{\"type\":\"function\",\"function\":{\"name\":\"fhir_request\",\"description\":\"Make a FHIR request...\",\"parameters\":{...}}}]"\
      }\
    ]
  }'

Response:

{
  "resourceType": "Parameters",
  "parameter": [\
    {\
      "name": "content",\
      "valueString": "I'll search for the patient with that phone number."\
    },\
    {\
      "name": "tool_calls",\
      "valueString": "[{\"id\":\"call_123\",\"type\":\"function\",\"function\":{\"name\":\"fhir_request\",\"arguments\":{\"method\":\"GET\",\"path\":\"Patient?phone=718-564-9483\"}}}]"\
    }\
  ]
}

Example: AI-Suggested Resource Creation

Request (asking AI to create a task):

{
  "resourceType": "Parameters",
  "parameter": [\
    {\
      "name": "messages",\
      "valueString": "[{\"role\":\"user\",\"content\":\"Create a task to fill up chart note\"}]"\
    },\
    {\
      "name": "model",\
      "valueString": "gpt-4"\
    },\
    {\
      "name": "tools",\
      "valueString": "[{\"type\":\"function\",\"function\":{\"name\":\"fhir_request\",\"description\":\"Make a FHIR request...\",\"parameters\":{...}}}]"\
    }\
  ]
}

Response:

{
  "resourceType": "Parameters",
  "parameter": [\
    {\
      "name": "content",\
      "valueString": "I'll create a task for filling up the chart note."\
    },\
    {\
      "name": "tool_calls",\
      "valueString": "[{\"id\":\"call_xyz\",\"type\":\"function\",\"function\":{\"name\":\"fhir_request\",\"arguments\":{\"method\":\"POST\",\"path\":\"Task\",\"body\":{\"resourceType\":\"Task\",\"status\":\"requested\",\"intent\":\"order\",\"description\":\"Fill up chart note\"}}}}]"\
    }\
  ]
}

Streaming Responses

For real-time conversational interfaces, you can enable streaming by setting the Accept header:

curl 'https://api.medplum.com/fhir/R4/$ai' \
  -X POST \
  -H "Content-Type: application/fhir+json" \
  -H "Accept: text/event-stream" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "resourceType": "Parameters",
    "parameter": [\
      {\
        "name": "messages",\
        "valueString": "[{\"role\":\"user\",\"content\":\"Explain FHIR resources\"}]"\
      },\
      {\
        "name": "model",\
        "valueString": "gpt-4"\
      }\
    ]
  }'

The response will be in Server-Sent Events (SSE) format:

data: {"content":"FHIR"}

data: {"content":" resources"}

data: {"content":" are"}

data: [DONE]

Note: Tool calls are not supported in streaming mode.

Using with TypeScript SDK

import { MedplumClient } from '@medplum/core';

const medplum = new MedplumClient();

// Simple conversation

const response = await medplum.fhirUrl('$ai').post({
  resourceType: 'Parameters',
  parameter: [\
    {\
      name: 'messages',\
      valueString: JSON.stringify([{ role: 'user', content: 'What is FHIR?' }]),\
    },\
    {\
      name: 'model',\
      valueString: 'gpt-4',\
    },\
  ],
});

const content = response.parameter?.find((p) => p.name === 'content')?.valueString;

console.log(content);

With Function Calling

const fhirTools = [\
  {\
    type: 'function' as const,\
    function: {\
      name: 'fhir_request',\
      description: 'Make a FHIR request to the Medplum server...',\
      parameters: {\
        type: 'object' as const,\
        properties: {\
          method: {\
            type: 'string' as const,\
            enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\
          },\
          path: {\
            type: 'string' as const,\
            description: 'FHIR resource path',\
          },\
          body: {\
            type: 'object' as const,\
            description: 'Resource body for POST/PUT/PATCH',\
          },\
        },\
        required: ['method', 'path'],\
      },\
    },\
  },\
];

const response = await medplum.fhirUrl('$ai').post({
  resourceType: 'Parameters',
  parameter: [\
    {\
      name: 'messages',\
      valueString: JSON.stringify([{ role: 'user', content: 'Create a patient named John Smith' }]),\
    },\
    {\
      name: 'model',\
      valueString: 'gpt-4',\
    },\
    {\
      name: 'tools',\
      valueString: JSON.stringify(fhirTools),\
    },\
  ],
});

// Check for tool calls

const toolCallsParam = response.parameter?.find((p) => p.name === 'tool_calls');

if (toolCallsParam?.valueString) {
  const toolCalls = JSON.parse(toolCallsParam.valueString);
  console.log('AI suggested:', toolCalls);
  // Execute the suggested FHIR operation
}

Security Considerations

  1. API Key Management: The OpenAI API key is stored as a project secret (OPENAI_API_KEY) and never exposed to clients. It is read server-side from project settings.

  2. Feature Gating: The AI feature must be explicitly enabled on your project. This prevents unauthorized usage.

  3. Tool Execution: The $ai operation returns suggested tool calls but does not execute them. Your application is responsible for:

    • Validating tool call parameters
    • Checking user permissions
    • Executing FHIR operations safely
  4. Rate Limiting: Consider implementing rate limiting to prevent excessive OpenAI API usage and costs.

  5. Input Validation: Always validate and sanitize user input before passing it to the AI operation.

Error Handling

API Key Not Configured

{
  "resourceType": "OperationOutcome",
  "issue": [\
    {\
      "severity": "error",\
      "code": "invalid",\
      "details": {\
        "text": "OpenAI API key not configured in project secrets"\
      }\
    }\
  ]
}

Invalid OpenAI API Key

{
  "resourceType": "OperationOutcome",
  "issue": [\
    {\
      "severity": "error",\
      "code": "invalid",\
      "details": {\
        "text": "Failed to call OpenAI API: OpenAI API error: 401 Unauthorized - Incorrect API key provided"\
      }\
    }\
  ]
}

Feature Not Enabled

{
  "resourceType": "OperationOutcome",
  "issue": [\
    {\
      "severity": "error",\
      "code": "forbidden",\
      "details": {\
        "text": "Forbidden"\
      }\
    }\
  ]
}

Limitations

Common Use Cases

  1. AI-Assisted Patient Search: Use natural language to search for patients
  2. Clinical Documentation: Generate structured clinical notes from conversational input
  3. FHIR Resource Creation: Create FHIR resources through natural language commands
  4. Data Extraction: Extract structured data from unstructured clinical text
  5. Patient Communication: Build AI-powered chatbots for patient engagement
  6. Clinical Decision Support: Provide AI-assisted recommendations based on patient data

Related Documentation