How TsgcAIChat asks a provider for JSON, and what each provider receives.
Structured output asks the provider for a JSON answer instead of prose, so the answer can be read with a JSON parser and mapped onto your own types. It is configured with three sub-properties of ChatOptions and read back with ChatJSON. Every provider the component supports is covered, each one with its own request shape, which the component builds for you.
ResponseFormat is rfText by default, and with that value the request is byte for byte the request the component always sent, so nothing changes for existing code.
Set the format, set the schema and call ChatJSON:
oChat.Provider := aicpOpenAI;
oChat.ChatOptions.ApiKey := 'API_KEY';
oChat.ChatOptions.Model := 'gpt-4o-mini';
oChat.ChatOptions.ResponseFormat := rfJSONSchema;
oChat.ChatOptions.JSONSchema := '{"type":"object","additionalProperties":false,' +
'"required":["city","celsius"],"properties":{' +
'"city":{"type":"string"},"celsius":{"type":"number"}}}';
oChat.ChatOptions.JSONSchemaName := 'weather';
vJSON := oChat.ChatJSON('Weather in Barcelona right now');
| Property | Type | Default | Description |
| ResponseFormat | TsgcAIChatResponseFormat | rfText | rfText, rfJSON or rfJSONSchema. |
| JSONSchema | string | ‘’ | The JSON Schema document. Required when ResponseFormat is rfJSONSchema, and it must itself be valid JSON. |
| JSONSchemaName | string | ‘response’ | Name sent with the schema. OpenAI, Grok and Mistral require one, and it is also the name of the forced tool of Anthropic. An empty value falls back to response. |
| ResponseFormat | What is asked for |
| rfText | The request is built exactly as it was built before structured output existed. This is the default. |
| rfJSON | Any valid JSON document is asked for. No schema is sent. |
| rfJSONSchema | A document that validates against ChatOptions.JSONSchema is asked for. |
| Provider | Request |
| aicpOpenAI | response_format with type: json_object for rfJSON, and with type: json_schema plus { name, schema, strict: true } for rfJSONSchema. |
| aicpGrok | The same request as OpenAI. |
| aicpMistral | The same request as OpenAI. |
| aicpOllama | The same request as OpenAI. |
| aicpDeepSeek | DeepSeek implements no json_schema mode, so both formats send response_format with type: json_object. With rfJSONSchema the schema travels in the system message instead. The instruction always contains the word json in lowercase, which DeepSeek requires when json_object is asked for. |
| aicpAnthropic | Anthropic has no JSON mode. rfJSON appends the instruction to the system message. rfJSONSchema sends one tool whose input_schema is the schema, plus tool_choice with that tool name, so the model is forced to call it, and the answer is read from the input object of the tool_use block. |
| aicpGemini | generationConfig.responseMimeType is set to application/json, and rfJSONSchema adds generationConfig.responseSchema. |
The instruction the two providers without a native schema mode receive is appended to SystemMessage for that request only. Your own SystemMessage is kept and the instruction is added on a new line.
ChatJSON sends the message with the configured format, reads the answer, checks that it parses as JSON and returns the JSON text. When it does not parse, the question is sent once more with the parse error appended, so the model can correct itself, and a second unparseable answer raises EsgcAIChat. Markdown fences are not stripped: an answer wrapped in a code fence is not valid JSON, so it goes through the same retry.
Unlike Chat, which reports a transport or provider error through OnChatError and returns an empty string, ChatJSON lets those errors propagate to the caller. Wrap the call in a try block:
try
vJSON := oChat.ChatJSON(vQuestion);
except
on E: EsgcAIChat do
// the provider did not answer with JSON, twice
on E: Exception do
// transport or provider error
end;
An answer that parses is added to the history as the assistant turn and raised through OnChatMessage, exactly as Chat does. An answer that does not parse is not added, so the history never keeps a broken document.
EsgcAIChat is the exception class of the component, declared in sgcAI_Chat. It is raised in four cases:
input_json_delta chunks, which the component does not assemble, so the combination is refused instead of returning an empty answer. Use ChatJSON, or set ResponseFormat to rfJSON.
Every other provider streams with structured output. The validation of the schema happens on the first request that uses it, not when the property is assigned.
ChatOptions.BaseUrl replaces the vendor endpoint base of the selected provider, so a gateway, a proxy or a local OpenAI compatible server can be used with any of them. It used to be honoured by Ollama alone. Trailing slashes are trimmed, and an empty value keeps the vendor endpoint untouched. With aicpOllama the value is the Host of the Ollama options, as before.
oChat.Provider := aicpOpenAI;
oChat.ChatOptions.BaseUrl := 'https://gateway.local/openai';
oChat.ChatOptions.ApiKey := 'API_KEY';