Skip to content

3. Diagnosing

Use an LLM to analyze errors and produce structured diagnosis.


Now that we can detect errors, let's use the LLM to diagnose them. This is where @Type structured output really shines — the model returns exactly the fields we need, verified by the compiler.

The diagnosis function

Ask your LLM to add this, or write it directly:

let diagnose error_report =
  diagnose_model @{severity: String, root_cause: String, suggested_fix: String}
  `You are a site reliability engineer. Analyze this error report from a service health check and provide a diagnosis.

Error report:
{{ error_report }}

Respond with:
- severity: "critical", "warning", or "info"
- root_cause: a brief explanation of what's likely wrong
- suggested_fix: a specific, actionable fix`;

A few things to notice:

  • @{severity: String, root_cause: String, suggested_fix: String} — the model must return exactly these fields. The compiler generates a JSON schema from this type annotation and sends it to the model API. If the model returns something that doesn't match, you get ParseError.
  • Template strings — the backtick string with {{ error_report }} interpolates the error report into the prompt. The interpolation must produce a String.
  • The return type is Result<{severity: String, root_cause: String, suggested_fix: String}, QosmError> ! {LLM.Call}.

Chaining with the monitor

Let's connect the monitoring and diagnosis steps:

let check_and_diagnose _ =
  match get_errors ∅ with (
  | Ok{value: report} ->
    match report with (
    | "No errors" -> Ok{value: {severity: "info", root_cause: "none", suggested_fix: "none"}}
    | errors -> diagnose errors)
  | Err{value: e} -> Err{value: e});

If there are no errors, we short-circuit with an "info" result. Otherwise, we send the error report to the model.

Check the effects

Look at the inferred type:

check_and_diagnose : Unit -> Result<{severity: String, root_cause: String, suggested_fix: String}, QosmError>
  ! {Http.Get, LLM.Call}

Both effects are there — Http.Get from service_get and LLM.Call from diagnose_model. The type system tracked them through two levels of function composition. This is what we meant when we said effects propagate automatically.

Run it

Run check_and_diagnose from the workspace. If the service has errors, you'll see a structured diagnosis with severity, root cause, and a suggested fix. If not, you'll see the "info" shortcircuit.

The model does the hard reasoning work, but the types guarantee the output shape. No JSON parsing surprises, no missing fields at runtime.

Next: Fixing →