Obscura includes provider-independent helpers for message lists. It does not depend on OpenAI, Anthropic, Gemini, LangChain, or another provider SDK.

messages = [
  %{role: "system", content: "Be concise."},
  %{role: "user", content: "Email jane@example.com"}
]

{:ok, safe_messages, vault} =
  Obscura.LLM.redact_messages(messages, vault: :memory, entities: [:email])

Only configured roles are redacted. The default role list is [:user].

safe_messages
#=> [
#=>   %{role: "system", content: "Be concise."},
#=>   %{role: "user", content: "Email <<EMAIL_001>>"}
#=> ]

Responses can be rehydrated with the same vault:

Obscura.LLM.rehydrate_response("I will contact <<EMAIL_001>>.", vault: vault)

Message maps with atom keys and string keys are supported. Unknown keys are preserved.

Explicit experimental NER options pass through to message redaction:

serving =
  Obscura.Recognizer.NER.FakeServing.new(%{
    "Alice" => [%{label: "PER", start: 0, end: 5, score: 0.9}]
  })

Obscura.LLM.redact_messages([%{role: :user, content: "Alice"}],
  vault: :memory,
  entities: [:person],
  recognizers: [{Obscura.Recognizer.NER, serving: serving}]
)

NER is never enabled implicitly.

Safety

Vault-backed prompts are reversible. The application must protect vault access and avoid logging raw prompts before redaction.