001/*
002 * Copyright (c) 2026, Oracle and/or its affiliates.
003 *
004 * Licensed under the Universal Permissive License Version 1.0 as shown at
005 * https://oss.oracle.com/licenses/upl/
006 */
007package com.oracle.database.selectai.model;
008
009import com.fasterxml.jackson.annotation.JsonInclude;
010import com.fasterxml.jackson.annotation.JsonIgnore;
011import com.fasterxml.jackson.databind.ObjectMapper;
012import com.fasterxml.jackson.databind.PropertyNamingStrategies;
013
014import java.util.ArrayList;
015import java.util.Collections;
016import java.util.HashMap;
017import java.util.List;
018import java.util.Locale;
019import java.util.Map;
020
021/**
022 * Attribute payload for {@code DBMS_CLOUD_AI.CREATE_PROFILE},
023 * {@code SET_ATTRIBUTE}, and {@code SET_ATTRIBUTES}.
024 * <p>
025 * Profile attributes describe the provider, AI model, credential, object
026 * selection, prompt behavior, conversation behavior, and RAG/vector-index
027 * settings that Select AI uses when answering prompts. Most attributes map to
028 * database-side Select AI profile settings; callers can build the payload once
029 * for profile creation or reuse it for bulk updates.
030 * <p>
031 * For complete runnable sample sources that build {@code ProfileAttributes},
032 * see
033 * <a href="{@docRoot}/src-html/com/oracle/database/selectai/samples/profile/CreateProfileSample.html">
034 * CreateProfileSample source</a> and
035 * <a href="{@docRoot}/src-html/com/oracle/database/selectai/samples/profile/SetProfileAttributesSample.html">
036 * SetProfileAttributesSample source</a>.
037 *
038 * <p>Commonly used profile attributes include:</p>
039 *
040 * <table>
041 *   <caption>Common Select AI profile attributes</caption>
042 *   <tr>
043 *     <th>Attribute</th>
044 *     <th>Description</th>
045 *   </tr>
046 *   <tr>
047 *     <td>{@code provider}</td>
048 *     <td>AI provider used by the profile.</td>
049 *   </tr>
050 *   <tr>
051 *     <td>{@code credential_name}</td>
052 *     <td>Credential used to access the AI provider, where required.</td>
053 *   </tr>
054 *   <tr>
055 *     <td>{@code model}</td>
056 *     <td>AI model used by the profile.</td>
057 *   </tr>
058 *   <tr>
059 *     <td>{@code object_list}</td>
060 *     <td>Database objects available for NL2SQL processing.</td>
061 *   </tr>
062 *   <tr>
063 *     <td>{@code object_list_mode}</td>
064 *     <td>Controls how database objects are selected for NL2SQL processing.</td>
065 *   </tr>
066 *   <tr>
067 *     <td>{@code temperature}</td>
068 *     <td>Controls randomness in model-generated responses.</td>
069 *   </tr>
070 *   <tr>
071 *     <td>{@code max_tokens}</td>
072 *     <td>Specifies the maximum number of tokens to generate.</td>
073 *   </tr>
074 *   <tr>
075 *     <td>{@code vector_index_name}</td>
076 *     <td>Specifies the vector index associated with the profile.</td>
077 *   </tr>
078 * </table>
079 *
080 * <p>
081 * The SDK performs basic, deterministic validation of profile attributes where
082 * the constraint can be validated independently of the configured provider or
083 * database version. This includes validation of supported SDK-defined values,
084 * numeric constraints, and basic input structure. Invalid values detected by
085 * the SDK result in an {@link IllegalArgumentException}.
086 *
087 * <p>
088 * Provider-specific, model-specific, database-version-specific, and other
089 * database-side semantic validation is delegated to {@code DBMS_CLOUD_AI} and
090 * Oracle Database. Therefore, an attribute value that passes SDK validation
091 * may still be rejected by the database based on the selected provider, model,
092 * database version, or other database-side requirements.
093 *
094 * <p>
095 * Supported profile attributes, valid values, defaults, requirements, and
096 * provider-specific behavior are defined by Oracle Database and may vary by
097 * database version and provider. See the {@code DBMS_CLOUD_AI} Profile
098 * Attributes documentation for the complete and current list.
099 *
100 * @see <a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/dbms-cloud-ai-package.html#GUID-12D91681-B51C-48E0-93FD-9ABC67B0F375">
101 *      DBMS_CLOUD_AI Profile Attributes</a>
102 * @see <a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/select-ai-manage-profiles.html">
103 *      Manage AI profiles</a>
104 */
105public final class ProfileAttributes {
106    /** Include table/column annotations (26ai) in augmented metadata. Default: false. */
107    private final Boolean annotations;
108    /** Additional prompt instructions supplied to Select AI. */
109    private final String additionalInstructions;
110    /** Azure OpenAI deployment name for chat/completion model. */
111    private final String azureDeploymentName;
112    /** Azure OpenAI deployment name for embedding model. */
113    private final String azureEmbeddingDeploymentName;
114    /** Azure OpenAI resource name. */
115    private final String azureResourceName;
116    /** Whether SQL value matching should be case-sensitive. Default: false. */
117    private final Boolean caseSensitiveValues;
118    /** Include table and column comments in metadata sent to LLM. Default: false. */
119    private final Boolean comments;
120    /** Include referential constraints in metadata. Default: false. */
121    private final Boolean constraints;
122    /** Enable conversation history for profile. Default: false. */
123    private final Boolean conversation;
124    /** Number of past turns retained when conversation is enabled. */
125    private final Integer conversationLength;
126    /** Credential name used to call provider APIs, where required. */
127    private final String credentialName;
128    /** Embedding model name. */
129    private final String embeddingModel;
130    /** Enables custom source URL metadata for RAG citations. Default: false. */
131    private final Boolean enableCustomSourceUri;
132    /** Restrict SQL generation to objects listed in objectList. Default: false. */
133    private final Boolean enforceObjectList;
134    /** Maximum tokens generated per response. */
135    private final Integer maxTokens;
136    /** Provider model identifier. */
137    private final String model;
138    /** JSON array of objects eligible for NL2SQL (owner mandatory, name optional). */
139    private final String objectList;
140    /** Metadata selection mode: all or automated. */
141    private final String objectListMode;
142    /** OCI API format for dedicated endpoint/model OCID (COHERE|GENERIC). */
143    private final String ociApiformat;
144    /** OCI compartment OCID. */
145    private final String ociCompartmentId;
146    /** OCI dedicated endpoint OCID. */
147    private final String ociEndpointId;
148    /** OCI runtime type (deprecated in favor of oci_apiformat). */
149    private final String ociRuntimetype;
150    /** AI provider name (mandatory). */
151    private final String provider;
152    /** Provider endpoint host/path for OpenAI-compatible providers. */
153    private final String providerEndpoint;
154    /** Provider region. */
155    private final String region;
156    /** Role prompt supplied to Select AI. */
157    private final String role;
158    /** Optional deterministic sampling seed (signed 64-bit integer). */
159    private final Long seed;
160    /** Stop sequences for generation. */
161    private final List<String> stopTokens;
162    /** Non-negative sampling temperature. */
163    private final Double temperature;
164    /** Vector index name (Oracle SQL identifier, up to 125 chars). */
165    private final String vectorIndexName;
166    /** Source language for translation. */
167    private final String sourceLanguage;
168    /** Target language for translation. */
169    private final String targetLanguage;
170    /** Unmodeled attributes preserved for metadata round-tripping. */
171    private final Map<String, String> customAttributes;
172
173    private ProfileAttributes(Builder builder) {
174        this.annotations = builder.annotations;
175        this.additionalInstructions = builder.additionalInstructions;
176        this.azureDeploymentName = builder.azureDeploymentName;
177        this.azureEmbeddingDeploymentName = builder.azureEmbeddingDeploymentName;
178        this.azureResourceName = builder.azureResourceName;
179        this.caseSensitiveValues = builder.caseSensitiveValues;
180        this.comments = builder.comments;
181        this.constraints = builder.constraints;
182        this.conversation = builder.conversation;
183        this.conversationLength = builder.conversationLength;
184        this.credentialName = builder.credentialName;
185        this.embeddingModel = builder.embeddingModel;
186        this.enableCustomSourceUri = builder.enableCustomSourceUri;
187        this.enforceObjectList = builder.enforceObjectList;
188        this.maxTokens = builder.maxTokens;
189        this.model = builder.model;
190        this.objectList = builder.objectList;
191        this.objectListMode = builder.objectListMode;
192        this.ociApiformat = builder.ociApiformat;
193        this.ociCompartmentId = builder.ociCompartmentId;
194        this.ociEndpointId = builder.ociEndpointId;
195        this.ociRuntimetype = builder.ociRuntimetype;
196        this.provider = builder.provider;
197        this.providerEndpoint = builder.providerEndpoint;
198        this.region = builder.region;
199        this.role = builder.role;
200        this.seed = builder.seed;
201        this.stopTokens = builder.stopTokens == null ? null : List.copyOf(builder.stopTokens);
202        this.temperature = builder.temperature;
203        this.vectorIndexName = builder.vectorIndexName;
204        this.sourceLanguage = builder.sourceLanguage;
205        this.targetLanguage = builder.targetLanguage;
206        this.customAttributes = Collections.unmodifiableMap(new HashMap<>(builder.customAttributes));
207    }
208
209    /**
210     * Creates a builder for profile attributes.
211     *
212     * @return new builder for constructing ProfileAttributes
213     */
214    public static Builder builder() {
215        return new Builder();
216    }
217
218    /**
219     * Returns whether table/column annotations are included in metadata.
220     *
221     * @return annotations flag value, or {@code null} when unset
222     */
223    public Boolean getAnnotations() {
224        return annotations;
225    }
226
227    /**
228     * Returns additional instructions included in Select AI prompts.
229     *
230     * @return additional instructions, or {@code null} when unset
231     */
232    public String getAdditionalInstructions() {
233        return additionalInstructions;
234    }
235
236    /**
237     * Returns the Azure OpenAI deployment name.
238     *
239     * @return Azure deployment name, or {@code null} when unset
240     */
241    public String getAzureDeploymentName() {
242        return azureDeploymentName;
243    }
244
245    /**
246     * Returns the Azure OpenAI embedding deployment name.
247     *
248     * @return Azure embedding deployment name, or {@code null} when unset
249     */
250    public String getAzureEmbeddingDeploymentName() {
251        return azureEmbeddingDeploymentName;
252    }
253
254    /**
255     * Returns the Azure OpenAI resource name.
256     *
257     * @return Azure resource name, or {@code null} when unset
258     */
259    public String getAzureResourceName() {
260        return azureResourceName;
261    }
262
263    /**
264     * Returns whether generated SQL value matching is case-sensitive.
265     *
266     * @return case-sensitive-values flag, or {@code null} when unset
267     */
268    public Boolean getCaseSensitiveValues() {
269        return caseSensitiveValues;
270    }
271
272    /**
273     * Returns whether table and column comments are included in prompt metadata.
274     *
275     * @return comments flag, or {@code null} when unset
276     */
277    public Boolean getComments() {
278        return comments;
279    }
280
281    /**
282     * Returns whether referential constraints are included in prompt metadata.
283     *
284     * @return constraints flag, or {@code null} when unset
285     */
286    public Boolean getConstraints() {
287        return constraints;
288    }
289
290    /**
291     * Returns whether conversation history is enabled for the profile.
292     *
293     * @return conversation flag, or {@code null} when unset
294     */
295    public Boolean getConversation() {
296        return conversation;
297    }
298
299    /**
300     * Returns how many conversation turns are retained for context.
301     *
302     * @return configured conversation length, or {@code null} when unset
303     */
304    public Integer getConversationLength() {
305        return conversationLength;
306    }
307
308    /**
309     * Returns the credential name used for provider API calls.
310     *
311     * @return credential name, or {@code null} when unset
312     */
313    public String getCredentialName() {
314        return credentialName;
315    }
316
317    /**
318     * Returns the embedding model name.
319     *
320     * @return embedding model name, or {@code null} when unset
321     */
322    public String getEmbeddingModel() {
323        return embeddingModel;
324    }
325
326    /**
327     * Returns whether custom source URI metadata is enabled.
328     *
329     * @return custom-source-URI flag, or {@code null} when unset
330     */
331    public Boolean getEnableCustomSourceUri() {
332        return enableCustomSourceUri;
333    }
334
335    /**
336     * Returns whether SQL generation is restricted to {@code object_list}.
337     *
338     * @return enforce-object-list flag, or {@code null} when unset
339     */
340    public Boolean getEnforceObjectList() {
341        return enforceObjectList;
342    }
343
344    /**
345     * Returns the maximum generated output tokens.
346     *
347     * @return maximum tokens value, or {@code null} when unset
348     */
349    public Integer getMaxTokens() {
350        return maxTokens;
351    }
352
353    /**
354     * Returns the provider model identifier.
355     *
356     * @return provider model identifier, or {@code null} when unset
357     */
358    public String getModel() {
359        return model;
360    }
361
362    /**
363     * Returns the JSON object list supplied to Select AI.
364     *
365     * @return object-list JSON string, or {@code null} when unset
366     */
367    public String getObjectList() {
368        return objectList;
369    }
370
371    /**
372     * Returns how Select AI chooses objects for metadata.
373     *
374     * @return object-list mode value, or {@code null} when unset
375     */
376    public String getObjectListMode() {
377        return objectListMode;
378    }
379
380    /**
381     * Returns the OCI Generative AI API format.
382     *
383     * @return OCI API format value, or {@code null} when unset
384     */
385    public String getOciApiformat() {
386        return ociApiformat;
387    }
388
389    /**
390     * Returns the OCI compartment OCID.
391     *
392     * @return OCI compartment ID, or {@code null} when unset
393     */
394    public String getOciCompartmentId() {
395        return ociCompartmentId;
396    }
397
398    /**
399     * Returns the OCI dedicated endpoint OCID.
400     *
401     * @return OCI endpoint ID, or {@code null} when unset
402     */
403    public String getOciEndpointId() {
404        return ociEndpointId;
405    }
406
407    /**
408     * Returns the OCI runtime type attribute.
409     *
410     * @return OCI runtime type value, or {@code null} when unset
411     */
412    public String getOciRuntimetype() {
413        return ociRuntimetype;
414    }
415
416    /**
417     * Returns the AI provider name.
418     *
419     * @return provider name, or {@code null} when unset
420     */
421    public String getProvider() {
422        return provider;
423    }
424
425    /**
426     * Returns the provider endpoint host/path.
427     *
428     * @return provider endpoint value, or {@code null} when unset
429     */
430    public String getProviderEndpoint() {
431        return providerEndpoint;
432    }
433
434    /**
435     * Returns the provider region.
436     *
437     * @return configured region value, or {@code null} when unset
438     */
439    public String getRegion() {
440        return region;
441    }
442
443    /**
444     * Returns the role prompt used by Select AI.
445     *
446     * @return role prompt, or {@code null} when unset
447     */
448    public String getRole() {
449        return role;
450    }
451
452    /**
453     * Returns the deterministic sampling seed.
454     *
455     * @return deterministic sampling seed, or {@code null} when unset
456     */
457    public Long getSeed() {
458        return seed;
459    }
460
461    /**
462     * Returns stop tokens used to terminate generation.
463     *
464     * @return configured stop-token list, or {@code null} when unset
465     */
466    public List<String> getStopTokens() {
467        return stopTokens;
468    }
469
470    /**
471     * Returns the provider sampling temperature.
472     *
473     * @return sampling temperature, or {@code null} when unset
474     */
475    public Double getTemperature() {
476        return temperature;
477    }
478
479    /**
480     * Returns the vector index name associated with this profile.
481     *
482     * @return vector index name, or {@code null} when unset
483     */
484    public String getVectorIndexName() {
485        return vectorIndexName;
486    }
487
488    /**
489     * Returns the source language for translation requests.
490     *
491     * @return source language for translation, or {@code null} when unset
492     */
493    public String getSourceLanguage() {
494        return sourceLanguage;
495    }
496
497    /**
498     * Returns the target language for translation requests.
499     *
500     * @return target language for translation, or {@code null} when unset
501     */
502    public String getTargetLanguage() {
503        return targetLanguage;
504    }
505
506    /**
507     * Returns unmodeled database attributes preserved for forward-compatible
508     * metadata round-tripping.
509     * <p>
510     * These values are emitted by {@link #toAttributeMap()} but excluded from
511     * {@link #toJson()} to avoid sending a nested {@code custom_attributes}
512     * object to {@code DBMS_CLOUD_AI}.
513     *
514     * @return immutable map of custom attribute names and values
515     */
516    @JsonIgnore
517    public Map<String, String> getCustomAttributes() {
518        return customAttributes;
519    }
520
521    /**
522     * Converts non-null attributes to DBMS_CLOUD_AI attribute names and string values.
523     *
524     * @return map containing non-null profile attributes
525     */
526    public Map<String, String> toAttributeMap() {
527        Map<String, String> map = new HashMap<>();
528        putIfNotNull(map, "annotations", annotations);
529        putIfNotNull(map, "additional_instructions", additionalInstructions);
530        putIfNotNull(map, "azure_deployment_name", azureDeploymentName);
531        putIfNotNull(map, "azure_embedding_deployment_name", azureEmbeddingDeploymentName);
532        putIfNotNull(map, "azure_resource_name", azureResourceName);
533        putIfNotNull(map, "case_sensitive_values", caseSensitiveValues);
534        putIfNotNull(map, "comments", comments);
535        putIfNotNull(map, "constraints", constraints);
536        putIfNotNull(map, "conversation", conversation);
537        putIfNotNull(map, "conversation_length", conversationLength);
538        putIfNotNull(map, "credential_name", credentialName);
539        putIfNotNull(map, "embedding_model", embeddingModel);
540        putIfNotNull(map, "enable_custom_source_uri", enableCustomSourceUri);
541        putIfNotNull(map, "enforce_object_list", enforceObjectList);
542        putIfNotNull(map, "max_tokens", maxTokens);
543        putIfNotNull(map, "model", model);
544        putIfNotNull(map, "object_list", objectList);
545        putIfNotNull(map, "object_list_mode", objectListMode);
546        putIfNotNull(map, "oci_apiformat", ociApiformat);
547        putIfNotNull(map, "oci_compartment_id", ociCompartmentId);
548        putIfNotNull(map, "oci_endpoint_id", ociEndpointId);
549        putIfNotNull(map, "oci_runtimetype", ociRuntimetype);
550        putIfNotNull(map, "provider", provider);
551        putIfNotNull(map, "provider_endpoint", providerEndpoint);
552        putIfNotNull(map, "region", region);
553        putIfNotNull(map, "role", role);
554        putIfNotNull(map, "seed", seed);
555        putStopTokensIfNotNull(map, stopTokens);
556        putIfNotNull(map, "temperature", temperature);
557        putIfNotNull(map, "vector_index_name", vectorIndexName);
558        putIfNotNull(map, "source_language", sourceLanguage);
559        putIfNotNull(map, "target_language", targetLanguage);
560        map.putAll(customAttributes);
561        return map;
562    }
563
564    /**
565     * Builds a {@link ProfileAttributes} instance from an attribute name/value map.
566     * <p>
567     * Recognized keys are converted to strongly-typed builder fields; unknown
568     * keys are accepted so metadata reads do not fail when the database exposes a
569     * newer attribute than this SDK version supports directly.
570     *
571     * @param attributes attribute map (typically DB-sourced key/value pairs)
572     * @return populated {@link ProfileAttributes} instance
573     */
574    public static ProfileAttributes fromAttributeMap(Map<String, String> attributes) {
575        Builder builder = ProfileAttributes.builder();
576        if (attributes == null || attributes.isEmpty()) {
577            return builder.build();
578        }
579
580        attributes.forEach((k, v) -> {
581            if (k == null || v == null) {
582                return;
583            }
584            String key = k.toLowerCase(Locale.ROOT);
585            switch (key) {
586                case "annotations" -> builder.annotations(Boolean.parseBoolean(v));
587                case "additional_instructions" -> builder.additionalInstructions(v);
588                case "azure_deployment_name" -> builder.azureDeploymentName(v);
589                case "azure_embedding_deployment_name" -> builder.azureEmbeddingDeploymentName(v);
590                case "azure_resource_name" -> builder.azureResourceName(v);
591                case "case_sensitive_values" -> builder.caseSensitiveValues(Boolean.parseBoolean(v));
592                case "comments" -> builder.comments(Boolean.parseBoolean(v));
593                case "constraints" -> builder.constraints(Boolean.parseBoolean(v));
594                case "conversation" -> builder.conversation(Boolean.parseBoolean(v));
595                case "conversation_length" -> builder.conversationLength(Integer.parseInt(v));
596                case "credential_name" -> builder.credentialName(v);
597                case "embedding_model" -> builder.embeddingModel(v);
598                case "enable_custom_source_uri" -> builder.enableCustomSourceUri(Boolean.parseBoolean(v));
599                case "enforce_object_list" -> builder.enforceObjectList(Boolean.parseBoolean(v));
600                case "max_tokens" -> builder.maxTokens(Integer.parseInt(v));
601                case "model" -> builder.model(v);
602                case "object_list" -> builder.objectList(v);
603                case "object_list_mode" -> builder.objectListMode(v);
604                case "oci_apiformat" -> builder.ociApiformat(v);
605                case "oci_compartment_id" -> builder.ociCompartmentId(v);
606                case "oci_endpoint_id" -> builder.ociEndpointId(v);
607                case "oci_runtimetype" -> builder.ociRuntimetype(v);
608                case "provider" -> builder.provider(v);
609                case "provider_endpoint" -> builder.providerEndpoint(v);
610                case "region" -> builder.region(v);
611                case "role" -> builder.role(v);
612                case "seed" -> builder.seed(Long.parseLong(v));
613                case "stop_tokens" -> builder.stopTokens(parseStopTokens(v));
614                case "temperature" -> builder.temperature(Double.parseDouble(v));
615                case "vector_index_name" -> builder.vectorIndexName(v);
616                case "source_language" -> builder.sourceLanguage(v);
617                case "target_language" -> builder.targetLanguage(v);
618                default -> builder.customAttribute(k, v);
619            }
620        });
621        return builder.build();
622    }
623
624    /**
625     * Adds a non-null profile attribute value to the output map.
626     *
627     * @param map destination attribute map
628     * @param key DBMS_CLOUD_AI attribute name
629     * @param value value to stringify and add
630     */
631    private static void putIfNotNull(Map<String, String> map, String key, Object value) {
632        if (value != null) {
633            map.put(key, String.valueOf(value));
634        }
635    }
636
637    /**
638     * Adds stop tokens as JSON array text so string-valued attribute maps can
639     * preserve token boundaries.
640     *
641     * @param map destination attribute map
642     * @param stopTokens stop-token list
643     */
644    private static void putStopTokensIfNotNull(Map<String, String> map, List<String> stopTokens) {
645        if (stopTokens == null) {
646            return;
647        }
648        try {
649            map.put("stop_tokens", new ObjectMapper().writeValueAsString(stopTokens));
650        } catch (Exception e) {
651            throw new IllegalStateException("Failed to serialize stopTokens", e);
652        }
653    }
654
655    /**
656     * Parses a stop_tokens attribute value serialized as a JSON array.
657     *
658     * @param value stop_tokens attribute value
659     * @return parsed stop-token list
660     */
661    private static List<String> parseStopTokens(String value) {
662        if (value == null || value.isBlank()) {
663            return null;
664        }
665        try {
666            ObjectMapper mapper = new ObjectMapper();
667            var root = mapper.readTree(value);
668            if (!root.isArray()) {
669                throw new IllegalArgumentException("stop_tokens must be a JSON array");
670            }
671            List<String> parsedStopTokens = new ArrayList<>();
672            for (var node : root) {
673                if (!node.isTextual()) {
674                    throw new IllegalArgumentException("stop_tokens must contain only string values");
675                }
676                parsedStopTokens.add(node.asText());
677            }
678            return parsedStopTokens;
679        } catch (IllegalArgumentException e) {
680            throw e;
681        } catch (Exception e) {
682            throw new IllegalArgumentException("stop_tokens must be valid JSON array text", e);
683        }
684    }
685
686    /**
687     * Serializes the profile attributes using snake_case JSON names.
688     *
689     * @return JSON representation of this ProfileAttributes instance
690     */
691    public String toJson() {
692        ObjectMapper mapper = new ObjectMapper();
693        try {
694            mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
695            mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
696            return mapper.writeValueAsString(this);
697        } catch (Exception e) {
698            throw new IllegalStateException("Failed to serialize ProfileAttributes to JSON", e);
699        }
700    }
701    
702    /**
703     * Builder for {@link ProfileAttributes}.
704     */
705    public static final class Builder {
706        /** Annotation metadata flag being assembled. */
707        private Boolean annotations;
708        /** Additional prompt instructions being assembled. */
709        private String additionalInstructions;
710        /** Azure OpenAI deployment name being assembled. */
711        private String azureDeploymentName;
712        /** Azure embedding deployment name being assembled. */
713        private String azureEmbeddingDeploymentName;
714        /** Azure resource name being assembled. */
715        private String azureResourceName;
716        /** Case-sensitive value matching flag being assembled. */
717        private Boolean caseSensitiveValues;
718        /** Table/column comments flag being assembled. */
719        private Boolean comments;
720        /** Referential constraints flag being assembled. */
721        private Boolean constraints;
722        /** Conversation support flag being assembled. */
723        private Boolean conversation;
724        /** Conversation context length being assembled. */
725        private Integer conversationLength;
726        /** Provider credential name being assembled. */
727        private String credentialName;
728        /** Embedding model name being assembled. */
729        private String embeddingModel;
730        /** Custom source URI flag being assembled. */
731        private Boolean enableCustomSourceUri;
732        /** Object list enforcement flag being assembled. */
733        private Boolean enforceObjectList;
734        /** Maximum output token count being assembled. */
735        private Integer maxTokens;
736        /** Provider model name being assembled. */
737        private String model;
738        /** Object list JSON being assembled. */
739        private String objectList;
740        /** Object list mode being assembled. */
741        private String objectListMode;
742        /** OCI API format being assembled. */
743        private String ociApiformat;
744        /** OCI compartment OCID being assembled. */
745        private String ociCompartmentId;
746        /** OCI endpoint OCID being assembled. */
747        private String ociEndpointId;
748        /** OCI runtime type being assembled. */
749        private String ociRuntimetype;
750        /** Provider name being assembled. */
751        private String provider;
752        /** Provider endpoint being assembled. */
753        private String providerEndpoint;
754        /** Provider region being assembled. */
755        private String region;
756        /** Role prompt being assembled. */
757        private String role;
758        /** Deterministic sampling seed being assembled. */
759        private Long seed;
760        /** Stop token list being assembled. */
761        private List<String> stopTokens;
762        /** Provider temperature being assembled. */
763        private Double temperature;
764        /** Unmodeled attributes accepted for forward compatibility. */
765        private Map<String, String> customAttributes;
766        /** Vector index name being assembled. */
767        private String vectorIndexName;
768        /** Translation source language being assembled. */
769        private String sourceLanguage;
770        /** Translation target language being assembled. */
771        private String targetLanguage;
772        /** Provider profile shortcut that populated this builder. */
773        private ProviderProfile providerProfile;
774
775        private Builder() {
776            this.customAttributes = new HashMap<>();
777        }
778
779        /**
780         * Sets whether table/column annotations are included in metadata.
781         *
782         * @param annotations annotation metadata flag
783         * @return this builder instance
784         */
785        public Builder annotations(Boolean annotations) {
786            this.annotations = annotations;
787            return this;
788        }
789
790        /**
791         * Sets additional instructions included in Select AI prompts.
792         *
793         * @param additionalInstructions additional prompt instructions
794         * @return this builder instance
795         */
796        public Builder additionalInstructions(String additionalInstructions) {
797            this.additionalInstructions = additionalInstructions;
798            return this;
799        }
800
801        /**
802         * Sets the Azure OpenAI deployment name for the generation model.
803         *
804         * @param azureDeploymentName Azure deployment name
805         * @return this builder instance
806         */
807        public Builder azureDeploymentName(String azureDeploymentName) {
808            this.azureDeploymentName = azureDeploymentName;
809            return this;
810        }
811
812        /**
813         * Sets the Azure OpenAI deployment name for the embedding model.
814         *
815         * @param azureEmbeddingDeploymentName Azure embedding deployment name
816         * @return this builder instance
817         */
818        public Builder azureEmbeddingDeploymentName(String azureEmbeddingDeploymentName) {
819            this.azureEmbeddingDeploymentName = azureEmbeddingDeploymentName;
820            return this;
821        }
822
823        /**
824         * Sets the Azure OpenAI resource name.
825         *
826         * @param azureResourceName Azure resource name
827         * @return this builder instance
828         */
829        public Builder azureResourceName(String azureResourceName) {
830            this.azureResourceName = azureResourceName;
831            return this;
832        }
833
834        /**
835         * Sets whether generated SQL value matching is case-sensitive.
836         *
837         * @param caseSensitiveValues case-sensitive values flag
838         * @return this builder instance
839         */
840        public Builder caseSensitiveValues(Boolean caseSensitiveValues) {
841            this.caseSensitiveValues = caseSensitiveValues;
842            return this;
843        }
844
845        /**
846         * Sets whether table and column comments are included in metadata.
847         *
848         * @param comments comments metadata flag
849         * @return this builder instance
850         */
851        public Builder comments(Boolean comments) {
852            this.comments = comments;
853            return this;
854        }
855
856        /**
857         * Sets whether referential constraints are included in metadata.
858         *
859         * @param constraints constraints metadata flag
860         * @return this builder instance
861         */
862        public Builder constraints(Boolean constraints) {
863            this.constraints = constraints;
864            return this;
865        }
866
867        /**
868         * Sets whether conversation history is enabled for the profile.
869         *
870         * @param conversation conversation flag
871         * @return this builder instance
872         */
873        public Builder conversation(Boolean conversation) {
874            this.conversation = conversation;
875            return this;
876        }
877
878        /**
879         * Sets how many conversation turns are retained for context.
880         *
881         * @param conversationLength positive conversation context length
882         * @return this builder instance
883         */
884        public Builder conversationLength(Integer conversationLength) {
885            if (conversationLength != null && conversationLength <= 0) {
886                throw new IllegalArgumentException("conversationLength must be greater than 0");
887            }
888            this.conversationLength = conversationLength;
889            return this;
890        }
891
892        /**
893         * Sets the database credential used for provider API calls.
894         *
895         * @param credentialName credential name
896         * @return this builder instance
897         */
898        public Builder credentialName(String credentialName) {
899            this.credentialName = credentialName;
900            return this;
901        }
902
903        /**
904         * Sets the embedding model name.
905         *
906         * @param embeddingModel embedding model name
907         * @return this builder instance
908         */
909        public Builder embeddingModel(String embeddingModel) {
910            this.embeddingModel = embeddingModel;
911            return this;
912        }
913
914        /**
915         * Sets whether custom source URI metadata is enabled.
916         *
917         * @param enableCustomSourceUri custom source URI flag
918         * @return this builder instance
919         */
920        public Builder enableCustomSourceUri(Boolean enableCustomSourceUri) {
921            this.enableCustomSourceUri = enableCustomSourceUri;
922            return this;
923        }
924
925        /**
926         * Sets whether generated SQL is restricted to the object list.
927         *
928         * @param enforceObjectList object list enforcement flag
929         * @return this builder instance
930         */
931        public Builder enforceObjectList(Boolean enforceObjectList) {
932            this.enforceObjectList = enforceObjectList;
933            return this;
934        }
935
936        /**
937         * Sets the maximum generated output tokens.
938         *
939         * @param maxTokens positive token limit
940         * @return this builder instance
941         */
942        public Builder maxTokens(Integer maxTokens) {
943            if (maxTokens != null && maxTokens <= 0) {
944                throw new IllegalArgumentException("maxTokens must be greater than 0");
945            }
946            this.maxTokens = maxTokens;
947            return this;
948        }
949
950        /**
951         * Sets the provider model identifier.
952         *
953         * @param model provider model identifier
954         * @return this builder instance
955         */
956        public Builder model(String model) {
957            this.model = model;
958            return this;
959        }
960
961        /**
962         * Sets the object list JSON used to constrain metadata.
963         *
964         * @param objectList JSON array containing object descriptors
965         * @return this builder instance
966         */
967        public Builder objectList(String objectList) {
968            this.objectList = objectList;
969            return this;
970        }
971
972        /**
973         * Sets the object-list selection mode used for NL2SQL processing.
974         *
975         * <p>A {@code null} value leaves the object-list mode unset. A non-null value
976         * must correspond to an object-list mode supported by the SDK; otherwise,
977         * this method throws an {@link IllegalArgumentException}.</p>
978         *
979         * @param objectListMode object-list mode, or {@code null} to leave the mode unset
980         * @return this builder instance
981         * @throws IllegalArgumentException if {@code objectListMode} is non-null and
982         *         does not correspond to a supported object-list mode
983         */
984        public Builder objectListMode(String objectListMode) {
985            ObjectListMode parsedObjectListMode =
986                    ObjectListMode.fromValue(objectListMode);
987            this.objectListMode = parsedObjectListMode == null ? null : parsedObjectListMode.getValue();
988            return this;
989        }
990
991        /**
992         * Sets the object-list selection mode used for NL2SQL processing.
993         *
994         * @param objectListMode object-list mode
995         * @return this builder instance
996         */
997        public Builder objectListMode(ObjectListMode objectListMode) {
998            this.objectListMode = objectListMode == null ? null : objectListMode.getValue();
999            return this;
1000        }
1001
1002        /**
1003         * Sets the OCI Generative AI API format.
1004         *
1005         * @param ociApiformat OCI API format, such as COHERE or GENERIC
1006         * @return this builder instance
1007         */
1008        public Builder ociApiformat(String ociApiformat) {
1009            OciApiFormat parsedOciApiFormat = OciApiFormat.fromValue(ociApiformat);
1010            this.ociApiformat = parsedOciApiFormat == null ? null : parsedOciApiFormat.getValue();
1011            return this;
1012        }
1013
1014        /**
1015         * Sets the OCI Generative AI API format.
1016         *
1017         * @param ociApiFormat OCI API format
1018         * @return this builder instance
1019         */
1020        public Builder ociApiformat(OciApiFormat ociApiFormat) {
1021            this.ociApiformat = ociApiFormat == null ? null : ociApiFormat.getValue();
1022            return this;
1023        }
1024
1025        /**
1026         * Sets the OCI compartment OCID.
1027         *
1028         * @param ociCompartmentId OCI compartment OCID
1029         * @return this builder instance
1030         */
1031        public Builder ociCompartmentId(String ociCompartmentId) {
1032            this.ociCompartmentId = ociCompartmentId;
1033            return this;
1034        }
1035
1036        /**
1037         * Sets the OCI dedicated endpoint OCID.
1038         *
1039         * @param ociEndpointId OCI endpoint OCID
1040         * @return this builder instance
1041         */
1042        public Builder ociEndpointId(String ociEndpointId) {
1043            this.ociEndpointId = ociEndpointId;
1044            return this;
1045        }
1046
1047        /**
1048         * Sets the OCI runtime type attribute.
1049         *
1050         * @param ociRuntimetype OCI runtime type
1051         * @return this builder instance
1052         */
1053        public Builder ociRuntimetype(String ociRuntimetype) {
1054            this.ociRuntimetype = ociRuntimetype;
1055            return this;
1056        }
1057
1058        /**
1059         * Sets the AI provider for the profile.
1060         *
1061         * <p>A {@code null} value leaves the provider unset. A non-null value must
1062         * correspond to a provider supported by the SDK; otherwise, this method
1063         * throws an {@link IllegalArgumentException}.</p>
1064         *
1065         * @param provider provider name, or {@code null} to leave the provider unset
1066         * @return this builder instance
1067         * @throws IllegalArgumentException if {@code provider} is non-null and does
1068         *         not correspond to a supported provider
1069         */
1070        public Builder provider(String provider) {
1071            if (provider == null) {
1072                this.provider = null;
1073                return this;
1074            }
1075
1076            Provider parsedProvider = Provider.fromValue(provider);
1077
1078            if (parsedProvider == null) {
1079                throw new IllegalArgumentException(
1080                        "Unsupported provider: " + provider);
1081            }
1082
1083            this.provider = parsedProvider.name();
1084            return this;
1085        }
1086
1087        /**
1088         * Sets the provider endpoint host/path.
1089         *
1090         * @param providerEndpoint provider endpoint
1091         * @return this builder instance
1092         */
1093        public Builder providerEndpoint(String providerEndpoint) {
1094            this.providerEndpoint = providerEndpoint;
1095            return this;
1096        }
1097
1098        /**
1099         * Sets the provider region.
1100         *
1101         * @param region provider region
1102         * @return this builder instance
1103         */
1104        public Builder region(String region) {
1105            this.region = region;
1106            return this;
1107        }
1108
1109        /**
1110         * Sets the role prompt used by Select AI.
1111         *
1112         * @param role role prompt
1113         * @return this builder instance
1114         */
1115        public Builder role(String role) {
1116            this.role = role;
1117            return this;
1118        }
1119
1120        /**
1121         * Sets the deterministic sampling seed.
1122         *
1123         * @param seed sampling seed
1124         * @return this builder instance
1125         */
1126        public Builder seed(Long seed) {
1127            this.seed = seed;
1128            return this;
1129        }
1130
1131        /**
1132         * Sets stop tokens that terminate generation.
1133         *
1134         * @param stopTokens stop token list
1135         * @return this builder instance
1136         */
1137        public Builder stopTokens(List<String> stopTokens) {
1138            this.stopTokens = stopTokens;
1139            return this;
1140        }
1141
1142        /**
1143         * Sets the provider sampling temperature.
1144         *
1145         * @param temperature non-negative temperature
1146         * @return this builder instance
1147         */
1148        public Builder temperature(Double temperature) {
1149            if (temperature != null && temperature < 0) {
1150                throw new IllegalArgumentException("temperature must be a non-negative float");
1151            }
1152            this.temperature = temperature;
1153            return this;
1154        }
1155
1156        /**
1157         * Sets the vector index name associated with the profile.
1158         *
1159         * @param vectorIndexName Oracle SQL identifier for the vector index
1160         * @return this builder instance
1161         */
1162        public Builder vectorIndexName(String vectorIndexName) {
1163            this.vectorIndexName = vectorIndexName;
1164            return this;
1165        }
1166
1167        /**
1168         * Sets the source language for translation.
1169         *
1170         * @param sourceLanguage source language name or code
1171         * @return this builder instance
1172         */
1173        public Builder sourceLanguage(String sourceLanguage) {
1174            this.sourceLanguage = sourceLanguage;
1175            return this;
1176        }
1177
1178        /**
1179         * Sets the target language for translation.
1180         *
1181         * @param targetLanguage target language name or code
1182         * @return this builder instance
1183         */
1184        public Builder targetLanguage(String targetLanguage) {
1185            this.targetLanguage = targetLanguage;
1186            return this;
1187        }
1188
1189        /**
1190         * Applies a provider profile shortcut to this builder.
1191         *
1192         * @param providerProfile provider profile to apply
1193         * @return this builder instance
1194         */
1195        public Builder providerProfile(ProviderProfile providerProfile) {
1196            if (providerProfile == null) {
1197                return this;
1198            }
1199            this.providerProfile = providerProfile;
1200            providerProfile.applyTo(this);
1201            return this;
1202        }
1203
1204        /**
1205         * Accepts an unmodeled profile attribute for forward compatibility.
1206         * <p>
1207         * Current typed JSON serialization only emits attributes represented by
1208         * this SDK. This method prevents unknown database attributes from failing a
1209         * read/round-trip through {@link #fromAttributeMap(Map)}.
1210         *
1211         * @param key DBMS_CLOUD_AI attribute name
1212         * @param value attribute value
1213         * @return this builder instance
1214         */
1215        public Builder customAttribute(String key, String value) {
1216            if (key != null && value != null) {
1217                this.customAttributes.put(key, value);
1218            }
1219            return this;
1220        }
1221
1222        /**
1223         * Builds immutable profile attributes.
1224         *
1225         * @return immutable ProfileAttributes instance built from current builder state
1226         */
1227        public ProfileAttributes build() {
1228            return new ProfileAttributes(this);
1229        }
1230    }
1231}