Summary
Add structured output mode to the LLM client using ReqLLM.generate_object! with a flattened LLM-friendly schema.
Background
The program wrapper (#58, #59) enables structured output by avoiding root-level anyOf. This issue adds the code to actually use structured output in E2E tests.
Text mode (current): Uses generate_text! with full JSON schema in prompt, cleans markdown fences from response.
Structured mode (new): Uses generate_object! with flattened schema, guarantees valid JSON structure from LLM.
Acceptance Criteria
Implementation Hints
to_llm_schema/0
A flattened schema optimized for LLM structured output:
- No
$ref recursion (LLMs struggle with recursive schemas)
- Limited nesting depth (3 levels)
- Supports common operations: pipe, load, literal, filter, reject, map, select, aggregations, comparisons
def to_llm_schema do
%{
"$schema" => "http://json-schema.org/draft-07/schema#",
"title" => "PTC DSL Program (LLM-Friendly)",
"type" => "object",
"properties" => %{
"program" => %{
"description" => "The PTC program - pipe, load, or literal operation",
"anyOf" => [pipe_schema(), load_schema(), literal_schema()]
}
},
"required" => ["program"],
"additionalProperties" => false
}
end
generate_program_structured!/1
def generate_program_structured!(task) do
ensure_api_key!()
prompt = build_structured_prompt(task)
schema = PtcRunner.Schema.to_llm_schema()
result = ReqLLM.generate_object!(@model, prompt, schema,
receive_timeout: @timeout,
openrouter_provider: %{require_parameters: true}
)
# Extract program from wrapper and return as JSON string
Jason.encode!(result["program"])
end
E2E Tests
Add tests similar to existing text mode tests:
describe "structured mode - LLM program generation" do
@describetag :e2e
test "generates valid filter program" do
task = "Filter products where price is greater than 10"
program_json = LLMClient.generate_program_structured!(task)
context = %{"input" => [%{"name" => "A", "price" => 5}, %{"name" => "B", "price" => 15}]}
assert {:ok, result, _} = PtcRunner.run(program_json, context: context)
assert length(result) == 1
end
end
Dependencies
Test Plan
- Run
mix test --include e2e - all E2E tests pass (both text and structured)
Summary
Add structured output mode to the LLM client using
ReqLLM.generate_object!with a flattened LLM-friendly schema.Background
The
programwrapper (#58, #59) enables structured output by avoiding root-levelanyOf. This issue adds the code to actually use structured output in E2E tests.Text mode (current): Uses
generate_text!with full JSON schema in prompt, cleans markdown fences from response.Structured mode (new): Uses
generate_object!with flattened schema, guarantees valid JSON structure from LLM.Acceptance Criteria
to_llm_schema/0function inlib/ptc_runner/schema.exgenerate_program_structured!/1intest/support/llm_client.ex:e2e)mix test --include e2eImplementation Hints
to_llm_schema/0A flattened schema optimized for LLM structured output:
$refrecursion (LLMs struggle with recursive schemas)generate_program_structured!/1E2E Tests
Add tests similar to existing text mode tests:
Dependencies
programwrapper as canonical PTC format: Updateto_json_schema/0#59 (Schema withprogramwrapper) should be completed firstprogramwrapper as canonical PTC format: Update All Tests #60 (Test updates) - can be done in parallelTest Plan
mix test --include e2e- all E2E tests pass (both text and structured)