Skip to content

[FEAT]: Department-Aware LLM Prompt Templating — Dynamic System Prompts per Agency #410

@waweruedwin8

Description

@waweruedwin8

Description

FireForm currently uses a single hardcoded system prompt for all LLM extractions,
regardless of which agency's form is being filled. This means the LLM has no knowledge
of which fields are relevant for a specific department — a county sheriff form needs
suspect descriptions, an EMS form needs patient vitals, a CAL FIRE form needs acres
burned and suppression method. A generic prompt forces the LLM to guess, resulting in
missed fields and inconsistent extraction quality across agencies.

This feature proposes a prompt templating engine that dynamically constructs the
Ollama system prompt at runtime based on the target agency's profile.


Rationale

Without agency-aware prompts, FireForm cannot reliably scale beyond a single department.
Every new agency onboarded with a new PDF template also needs a tailored extraction
strategy — otherwise the LLM extracts generic fields and misses agency-specific ones.

This is the missing architectural link between the PDF template registry (what fields
need filling) and the LLM extraction layer (what fields to look for in the transcript).
Currently these two layers are disconnected. This proposal connects them.

Real example: CAL FIRE requires fire_cause, acres_burned, suppression_method,
and structures_threatened. None of these will be reliably extracted by a generic prompt
designed for a master incident schema.


Proposed Solution

  • Logic change in src/ — new PromptTemplateEngine class that loads agency config
    and builds a targeted system prompt before each Ollama call
  • New prompt for Mistral/Ollama — per-agency .txt prompt templates stored in
    src/prompts/
  • Update to requirements.txt — no new dependencies needed (PyYAML already present)

Agency config extension (YAML):

# agency_templates/cal_fire.yaml
agency_id: cal_fire
name: CAL FIRE
prompt_template: prompts/cal_fire.txt
required_fields:
  - incident_type
  - fire_cause
  - suppression_method
  - acres_burned
  - structures_threatened
extraction_hints:
  fire_cause: "Look for phrases like 'started by', 'ignited', 'caused by'"
  acres_burned: "Extract numeric values near words like 'acres', 'hectares'"

Prompt engine sketch:

class PromptTemplateEngine:
    def build_system_prompt(self, agency_id: str) -> str:
        agency_config = self._load_agency_config(agency_id)
        base_prompt = self._load_prompt_template(
            agency_config["prompt_template"]
        )
        fields_block = self._format_required_fields(
            agency_config["required_fields"],
            agency_config.get("extraction_hints", {})
        )
        return f"{base_prompt}\n\nRequired fields:\n{fields_block}"

Backward compatible — falls back to default prompt if no agency config exists.


Acceptance Criteria

  • Feature works in Docker container
  • Documentation updated in docs/
  • JSON output validates against the schema
  • PromptTemplateEngine loads correct prompt for a given agency_id
  • Falls back gracefully to default prompt when no agency config is found
  • At least 2 sample agency prompt templates included (e.g. CAL FIRE, EMS)
  • Unit tests cover prompt construction and fallback behavior

Additional Context

Related issues:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions