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