Foresight.LLMs.StructuredRecovery (Foresight v0.1.0)

Copy Markdown View Source

Recover structured JSON from local-LLM output that strict schema parsing rejected.

Ollama-served models routinely wrap their JSON object in a json fence and occasionally leave a trailing comma before a closing brace/bracket, despite being sent a response_format. LlmCore's strict structured-output parse then fails even though the body is otherwise valid and complete. The error tuple carries the raw body, so this module best-effort strips the fence + trailing commas and re-decodes. Shared by retain fact extraction and consolidation.

Summary

Functions

Strip a surrounding markdown code fence and trailing commas, then JSON-decode. Best-effort: returns {:ok, term} or {:error, reason} (from Jason.decode/1).

Pull the raw model body out of the known structured-output error shapes.

Try to decode a valid map from the error reason a complete_json_schema call returned. Returns {:ok, decoded_map} or :error.

Like recover/1, but casts the recovered map into module's struct using the same string-key → struct-field mapping Foresight.LLMs.LlmCore.complete_structured uses on the happy path. Lets the reflect agentic loop's structured calls survive a JSON-fenced body from models like gpt-oss:20b instead of hard-failing the whole reflect (fairness). Returns {:ok, struct} or :error so the caller keeps its original error when recovery is impossible.

Functions

decode_lenient(raw)

@spec decode_lenient(String.t()) :: {:ok, term()} | {:error, term()}

Strip a surrounding markdown code fence and trailing commas, then JSON-decode. Best-effort: returns {:ok, term} or {:error, reason} (from Jason.decode/1).

raw_from_error(arg1)

@spec raw_from_error(term()) :: String.t() | nil

Pull the raw model body out of the known structured-output error shapes.

recover(reason)

@spec recover(term()) :: {:ok, map()} | :error

Try to decode a valid map from the error reason a complete_json_schema call returned. Returns {:ok, decoded_map} or :error.

recover_struct(reason, module)

@spec recover_struct(term(), module()) :: {:ok, struct()} | :error

Like recover/1, but casts the recovered map into module's struct using the same string-key → struct-field mapping Foresight.LLMs.LlmCore.complete_structured uses on the happy path. Lets the reflect agentic loop's structured calls survive a JSON-fenced body from models like gpt-oss:20b instead of hard-failing the whole reflect (fairness). Returns {:ok, struct} or :error so the caller keeps its original error when recovery is impossible.