Skip to content

Commit 1395ec0

Browse files
committed
Structured output improvements, tests and samples
1 parent 30617d0 commit 1395ec0

14 files changed

Lines changed: 624 additions & 64 deletions

src/packages/uc_ai_openai.pkb

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,6 @@ create or replace package body uc_ai_openai as
530530
, p_model in uc_ai.model_type
531531
, p_max_tool_calls in pls_integer
532532
, p_schema in json_object_t default null
533-
, p_schema_name in varchar2 default 'structured_output'
534-
, p_strict in boolean default true
535533
) return json_object_t
536534
as
537535
l_scope uc_ai_logger.scope := c_scope_prefix || 'generate_text_chat_api';
@@ -573,8 +571,7 @@ create or replace package body uc_ai_openai as
573571
if p_schema is not null then
574572
l_response_format := uc_ai_structured_output.to_openai_format(
575573
p_schema => p_schema,
576-
p_name => p_schema_name,
577-
p_strict => p_strict
574+
p_strict => true
578575
);
579576
l_input_obj.put('response_format', l_response_format);
580577
end if;
@@ -648,8 +645,6 @@ create or replace package body uc_ai_openai as
648645
, p_model in uc_ai.model_type
649646
, p_max_tool_calls in pls_integer
650647
, p_schema in json_object_t default null
651-
, p_schema_name in varchar2 default 'structured_output'
652-
, p_strict in boolean default true
653648
) return json_object_t
654649
as
655650
begin
@@ -663,17 +658,13 @@ create or replace package body uc_ai_openai as
663658
, p_model => p_model
664659
, p_max_tool_calls => p_max_tool_calls
665660
, p_schema => p_schema
666-
, p_schema_name => p_schema_name
667-
, p_strict => p_strict
668661
);
669662
else
670663
return generate_text_chat_api(
671664
p_messages => p_messages
672665
, p_model => p_model
673666
, p_max_tool_calls => p_max_tool_calls
674667
, p_schema => p_schema
675-
, p_schema_name => p_schema_name
676-
, p_strict => p_strict
677668
);
678669
end if;
679670

src/packages/uc_ai_openai.pks

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ create or replace package uc_ai_openai as
6363
, p_model in uc_ai.model_type
6464
, p_max_tool_calls in pls_integer
6565
, p_schema in json_object_t default null
66-
, p_schema_name in varchar2 default 'structured_output'
67-
, p_strict in boolean default true
6866
) return json_object_t;
6967

7068
/*

src/packages/uc_ai_responses_api.pkb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,6 @@ create or replace package body uc_ai_responses_api as
501501
, p_model in uc_ai.model_type
502502
, p_max_tool_calls in pls_integer
503503
, p_schema in json_object_t default null
504-
, p_schema_name in varchar2 default 'structured_output'
505-
, p_strict in boolean default true
506504
) return json_object_t
507505
as
508506
l_scope uc_ai_logger.scope := c_scope_prefix || 'generate_text';
@@ -575,10 +573,9 @@ create or replace package body uc_ai_responses_api as
575573
if p_schema is not null then
576574
l_text_config := json_object_t();
577575

578-
l_response_format := uc_ai_structured_output.to_openai_format(
576+
l_response_format := uc_ai_structured_output.to_responses_api_format(
579577
p_schema => p_schema,
580-
p_name => p_schema_name,
581-
p_strict => p_strict
578+
p_strict => true
582579
);
583580

584581
l_text_config.put('format', l_response_format);

src/packages/uc_ai_responses_api.pks

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ create or replace package uc_ai_responses_api as
5252
* p_model: Model to use for generation
5353
* p_max_tool_calls: Maximum number of tool calls to allow in one request
5454
* p_schema: Optional JSON schema for structured output
55-
* p_schema_name: Name for the structured output schema
56-
* p_strict: Whether to enforce strict schema validation
5755
*
5856
* Returns: JSON object with response data including:
5957
* - id: Unique response ID
@@ -67,8 +65,6 @@ create or replace package uc_ai_responses_api as
6765
, p_model in uc_ai.model_type
6866
, p_max_tool_calls in pls_integer
6967
, p_schema in json_object_t default null
70-
, p_schema_name in varchar2 default 'structured_output'
71-
, p_strict in boolean default true
7268
) return json_object_t;
7369

7470
end uc_ai_responses_api;

src/packages/uc_ai_structured_output.pkb

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,23 @@ create or replace package body uc_ai_structured_output as
154154
*/
155155
function to_openai_format(
156156
p_schema in json_object_t,
157-
p_name in varchar2 default 'structured_output',
158157
p_strict in boolean default true
159158
) return json_object_t
160159
as
161160
l_scope uc_ai_logger.scope := c_scope_prefix || 'to_openai_format';
162161
l_response_format json_object_t := json_object_t();
163162
l_schema_copy json_object_t;
164163
l_json_schema json_object_t;
164+
l_name varchar2(4000 char) := 'structured_output';
165165
begin
166-
uc_ai_logger.log('Converting schema to OpenAI format', l_scope, 'Name: ' || p_name || ', Strict: ' || case when p_strict then 'true' else 'false' end);
166+
uc_ai_logger.log('Converting schema to OpenAI format', l_scope, ' Strict: ' || case when p_strict then 'true' else 'false' end);
167+
168+
if p_schema.has('title') then
169+
l_name := replace(p_schema.get_string('title'), ' ', '_');
170+
-- make it match [a-zA-Z0-9_-]+
171+
l_name := regexp_replace(l_name, '[^a-zA-Z0-9_-]', null);
172+
l_name := coalesce(l_name, 'structured_output');
173+
end if;
167174

168175
-- Create a copy of the input schema and process for strict mode
169176
if p_strict then
@@ -175,7 +182,7 @@ create or replace package body uc_ai_structured_output as
175182
l_response_format.put('type', 'json_schema');
176183

177184
l_json_schema := json_object_t();
178-
l_json_schema.put('name', p_name);
185+
l_json_schema.put('name', l_name);
179186
l_json_schema.put('schema', l_schema_copy);
180187
l_json_schema.put('strict', p_strict);
181188

@@ -221,13 +228,51 @@ create or replace package body uc_ai_structured_output as
221228
return l_schema_copy;
222229
end to_ollama_format;
223230

231+
/*
232+
* Convert a standard JSON schema to Responses API format for structured output
233+
*/
234+
function to_responses_api_format(
235+
p_schema in json_object_t,
236+
p_strict in boolean default true
237+
) return json_object_t
238+
as
239+
l_scope uc_ai_logger.scope := c_scope_prefix || 'to_responses_api_format';
240+
l_response_format json_object_t := json_object_t();
241+
l_schema_copy json_object_t;
242+
l_name varchar2(4000 char) := 'structured_output';
243+
begin
244+
uc_ai_logger.log('Converting schema to Responses API format', l_scope, ' Strict: ' || case when p_strict then 'true' else 'false' end);
245+
246+
if p_schema.has('title') then
247+
l_name := replace(p_schema.get_string('title'), ' ', '_');
248+
-- make it match [a-zA-Z0-9_-]+
249+
l_name := regexp_replace(l_name, '[^a-zA-Z0-9_-]', null);
250+
l_name := coalesce(l_name, 'structured_output');
251+
end if;
252+
253+
-- Create a copy of the input schema and process for strict mode
254+
if p_strict then
255+
l_schema_copy := process_openai_strict_schema(p_schema);
256+
else
257+
l_schema_copy := json_object_t(p_schema.to_clob);
258+
end if;
259+
260+
l_response_format.put('type', 'json_schema');
261+
l_response_format.put('name', l_name);
262+
l_response_format.put('strict', p_strict);
263+
l_response_format.put('schema', l_schema_copy);
264+
265+
uc_ai_logger.log('Responses API format conversion complete', l_scope, l_response_format.to_clob);
266+
return l_response_format;
267+
end to_responses_api_format;
268+
269+
224270
/*
225271
* Generic function to convert schema based on provider
226272
*/
227273
function format_schema(
228274
p_schema in json_object_t,
229275
p_provider in uc_ai.provider_type,
230-
p_name in varchar2 default 'structured_output',
231276
p_strict in boolean default true
232277
) return json_object_t
233278
as
@@ -238,7 +283,7 @@ create or replace package body uc_ai_structured_output as
238283

239284
case p_provider
240285
when uc_ai.c_provider_openai then
241-
l_result := to_openai_format(p_schema, p_name, p_strict);
286+
l_result := to_openai_format(p_schema, p_strict);
242287
when uc_ai.c_provider_google then
243288
l_result := to_google_format(p_schema);
244289
when uc_ai.c_provider_ollama then

src/packages/uc_ai_structured_output.pks

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@ create or replace package uc_ai_structured_output as
1313
* Convert a standard JSON schema to OpenAI format for structured output
1414
*
1515
* p_schema: Standard JSON schema as json_object_t
16-
* p_name: Optional name for the schema (required for OpenAI)
1716
* p_strict: Whether to use strict mode (OpenAI-specific)
1817
*
1918
* Returns: OpenAI-formatted response_format object
2019
*/
2120
function to_openai_format(
2221
p_schema in json_object_t,
23-
p_name in varchar2 default 'structured_output',
2422
p_strict in boolean default true
2523
) return json_object_t;
2624

@@ -46,20 +44,32 @@ create or replace package uc_ai_structured_output as
4644
p_schema in json_object_t
4745
) return json_object_t;
4846

47+
/*
48+
* Convert a standard JSON schema to Responses API format for structured output
49+
*
50+
* p_schema: Standard JSON schema as json_object_t
51+
* p_strict: Whether to use strict mode (OpenAI-specific)
52+
*
53+
* Returns: Responses API-formatted response_format object
54+
*/
55+
function to_responses_api_format(
56+
p_schema in json_object_t,
57+
p_strict in boolean default true
58+
) return json_object_t;
59+
60+
4961
/*
5062
* Generic function to convert schema based on provider
5163
*
5264
* p_schema: Standard JSON schema as json_object_t
5365
* p_provider: AI provider ('openai', 'google', 'ollama')
54-
* p_name: Optional name for the schema (used by OpenAI)
5566
* p_strict: Whether to use strict mode (OpenAI-specific)
5667
*
5768
* Returns: Provider-specific formatted schema
5869
*/
5970
function format_schema(
6071
p_schema in json_object_t,
6172
p_provider in uc_ai.provider_type,
62-
p_name in varchar2 default 'structured_output',
6373
p_strict in boolean default true
6474
) return json_object_t;
6575

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,44 @@
11
{
22
"model": "gpt-4o-mini",
3-
"response_format": {
4-
"type": "json_schema",
5-
"json_schema": {
6-
"name": "structured_output",
3+
"input": [
4+
{
5+
"type": "message",
6+
"role": "user",
7+
"content": [
8+
{
9+
"text": "What is the capital of France? Please respond with confidence.",
10+
"type": "input_text"
11+
}
12+
]
13+
}
14+
],
15+
"instructions": "You are a helpful assistant that provides accurate information.",
16+
"text": {
17+
"format": {
18+
"type": "json_schema",
19+
"name": "Response_with_confidence_score",
20+
"strict": true,
721
"schema": {
822
"type": "object",
23+
"additionalProperties": false,
924
"properties": {
1025
"response": {
11-
"type": "string",
12-
"description": "The generated response text"
26+
"type": "string"
1327
},
1428
"confidence": {
1529
"type": "number",
16-
"description": "Confidence score between 0 and 1",
1730
"minimum": 0,
1831
"maximum": 1
1932
}
2033
},
2134
"required": [
2235
"response",
2336
"confidence"
24-
],
25-
"additionalProperties": false
26-
},
27-
"strict": true
28-
}
29-
},
30-
"messages": [
31-
{
32-
"role": "system",
33-
"content": "You are a helpful assistant that provides accurate information."
37+
]
38+
}
3439
},
35-
{
36-
"role": "user",
37-
"content": [
38-
{
39-
"type": "text",
40-
"text": "What is the capital of France? Please respond with confidence."
41-
}
42-
]
43-
}
44-
]
40+
"verbosity": "medium"
41+
},
42+
"max_tool_calls": 10,
43+
"store": true
4544
}

test/samples/openai/chat/4-structured-output-response.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "chatcmpl-CAwhAgRBJ7oatXs6KSlwbTYoiMLgH",
33
"object": "chat.completion",
4-
"created": 1756724940,
4+
"created": 1768840409,
55
"model": "gpt-4o-mini-2024-07-18",
66
"choices": [
77
{
@@ -17,9 +17,9 @@
1717
}
1818
],
1919
"usage": {
20-
"prompt_tokens": 84,
20+
"prompt_tokens": 72,
2121
"completion_tokens": 16,
22-
"total_tokens": 100,
22+
"total_tokens": 88,
2323
"prompt_tokens_details": {
2424
"cached_tokens": 0,
2525
"audio_tokens": 0
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"model": "gpt-4o-mini",
3+
"response_format": {
4+
"type": "json_schema",
5+
"json_schema": {
6+
"name": "Response_with_confidence_score",
7+
"schema": {
8+
"type": "object",
9+
"additionalProperties": false,
10+
"properties": {
11+
"response": {
12+
"type": "string"
13+
},
14+
"confidence": {
15+
"type": "number",
16+
"minimum": 0,
17+
"maximum": 1
18+
}
19+
},
20+
"required": [
21+
"response",
22+
"confidence"
23+
]
24+
},
25+
"strict": true
26+
}
27+
},
28+
"messages": [
29+
{
30+
"role": "system",
31+
"content": "You are a helpful assistant that provides accurate information."
32+
},
33+
{
34+
"role": "user",
35+
"content": [
36+
{
37+
"type": "text",
38+
"text": "What is the capital of France? Please respond with confidence."
39+
}
40+
]
41+
}
42+
]
43+
}

0 commit comments

Comments
 (0)