This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit a054da0bc932b006fe7e379c72e8f90018d4c93d Author: Luigi De Masi <[email protected]> AuthorDate: Wed Sep 30 20:29:42 2026 +0200 CAMEL-25201: Make the TypeSafe AI API path configurable Expose apiPath on component and endpoint configuration with /v1/systemone as the default. Preserve base URL prefixes and reuse the managed endpoint configuration for semantic routes. Cover URL construction and configuration inheritance with HTTP tests, and update documentation and generated metadata. Co-authored-by: Codex <[email protected]> Signed-off-by: Luigi De Masi <[email protected]> --- .../camel/catalog/components/typesafe-ai.json | 58 ++++----- .../camel/catalog/docs/typesafe-ai-component.adoc | 24 +++- .../typesafeai/TypeSafeAiComponentConfigurer.java | 6 + .../typesafeai/TypeSafeAiEndpointConfigurer.java | 6 + .../typesafeai/TypeSafeAiEndpointUriFactory.java | 3 +- .../camel/component/typesafeai/typesafe-ai.json | 58 ++++----- .../src/main/docs/typesafe-ai-component.adoc | 24 +++- .../component/typesafeai/TypeSafeAiClient.java | 4 +- .../typesafeai/TypeSafeAiConfiguration.java | 25 +++- .../typesafeai/TypeSafeAiApiPathTest.java | 138 +++++++++++++++++++++ .../typesafeai/TypeSafeAiTestSupport.java | 2 +- .../dsl/TypesafeAiComponentBuilderFactory.java | 25 +++- .../dsl/TypeSafeAiEndpointBuilderFactory.java | 22 +++- 13 files changed, 328 insertions(+), 67 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/typesafe-ai.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/typesafe-ai.json index 96a854019318..18c9bff96dd6 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/typesafe-ai.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/typesafe-ai.json @@ -24,36 +24,38 @@ "remote": true }, "componentProperties": { - "baseUrl": { "index": 0, "kind": "property", "displayName": "Base Url", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "https:\/\/api.typesafe.ai", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API base URL. The client appends \/v1\/systemone. Redirects [...] - "configuration": { "index": 1, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Default configuration shared by TypeSafe AI endpoints." }, - "maxConcurrentRequests": { "index": 2, "kind": "property", "displayName": "Max Concurrent Requests", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 64, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Maximum concurrent evaluations per endpoint, shared by producers and pre [...] - "model": { "index": 3, "kind": "property", "displayName": "Model", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "jev-latest", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The model ID or alias. Use a versioned ID to pin decision behavior." }, - "questions": { "index": 4, "kind": "property", "displayName": "Questions", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "A JSON object mapping question names to Noul, Choice or Score question objects. When set, producers evalua [...] - "questionsResource": { "index": 5, "kind": "property", "displayName": "Questions Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Camel resource URI for a UTF-8 JSON object mapping question names to Noul, Choice or Scor [...] - "requestTimeout": { "index": 6, "kind": "property", "displayName": "Request Timeout", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The timeout in milliseconds for the complete HTTP request and response body. Must b [...] - "state": { "index": 7, "kind": "property", "displayName": "State", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The Simple expression selecting state for configured producer questions and the TypeSafe AI language. If not set, [...] - "lazyStartProducer": { "index": 8, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] - "resultProperty": { "index": 9, "kind": "property", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Store the producer response in this exchange property, preserving the original message body." }, - "autowiredEnabled": { "index": 10, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching [...] - "threshold": { "index": 11, "kind": "property", "displayName": "Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.5, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default inclusive probability threshold for the TypeSafe AI language. Must be within 0,1." }, - "uncertainty": { "index": 12, "kind": "property", "displayName": "Uncertainty", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.0, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default half-width of the inclusive uncertainty band for the TypeSafe AI language. Zer [...] - "uncertaintyPolicy": { "index": 13, "kind": "property", "displayName": "Uncertainty Policy", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.language.typesafeai.TypeSafeAiLanguage.UncertaintyPolicy", "enum": [ "NonMatch", "Fail" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NonMatch", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "c [...] - "apiKey": { "index": 14, "kind": "property", "displayName": "Api Key", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API key used for Bearer authentication." } + "apiPath": { "index": 0, "kind": "property", "displayName": "Api Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "\/v1\/systemone", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API path appended to baseUrl. A leading slash is optional. Must be a n [...] + "baseUrl": { "index": 1, "kind": "property", "displayName": "Base Url", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "https:\/\/api.typesafe.ai", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API base URL. The client appends apiPath, preserving any bas [...] + "configuration": { "index": 2, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Default configuration shared by TypeSafe AI endpoints." }, + "maxConcurrentRequests": { "index": 3, "kind": "property", "displayName": "Max Concurrent Requests", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 64, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Maximum concurrent evaluations per endpoint, shared by producers and pre [...] + "model": { "index": 4, "kind": "property", "displayName": "Model", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "jev-latest", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The model ID or alias. Use a versioned ID to pin decision behavior." }, + "questions": { "index": 5, "kind": "property", "displayName": "Questions", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "A JSON object mapping question names to Noul, Choice or Score question objects. When set, producers evalua [...] + "questionsResource": { "index": 6, "kind": "property", "displayName": "Questions Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Camel resource URI for a UTF-8 JSON object mapping question names to Noul, Choice or Scor [...] + "requestTimeout": { "index": 7, "kind": "property", "displayName": "Request Timeout", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The timeout in milliseconds for the complete HTTP request and response body. Must b [...] + "state": { "index": 8, "kind": "property", "displayName": "State", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The Simple expression selecting state for configured producer questions and the TypeSafe AI language. If not set, [...] + "lazyStartProducer": { "index": 9, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] + "resultProperty": { "index": 10, "kind": "property", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Store the producer response in this exchange property, preserving the original message body." }, + "autowiredEnabled": { "index": 11, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching [...] + "threshold": { "index": 12, "kind": "property", "displayName": "Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.5, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default inclusive probability threshold for the TypeSafe AI language. Must be within 0,1." }, + "uncertainty": { "index": 13, "kind": "property", "displayName": "Uncertainty", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.0, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default half-width of the inclusive uncertainty band for the TypeSafe AI language. Zer [...] + "uncertaintyPolicy": { "index": 14, "kind": "property", "displayName": "Uncertainty Policy", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.language.typesafeai.TypeSafeAiLanguage.UncertaintyPolicy", "enum": [ "NonMatch", "Fail" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NonMatch", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "c [...] + "apiKey": { "index": 15, "kind": "property", "displayName": "Api Key", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API key used for Bearer authentication." } }, "properties": { "name": { "index": 0, "kind": "path", "displayName": "Name", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "A logical name for the evaluation endpoint." }, - "baseUrl": { "index": 1, "kind": "parameter", "displayName": "Base Url", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "https:\/\/api.typesafe.ai", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API base URL. The client appends \/v1\/systemone. Redirects [...] - "maxConcurrentRequests": { "index": 2, "kind": "parameter", "displayName": "Max Concurrent Requests", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 64, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Maximum concurrent evaluations per endpoint, shared by producers and pr [...] - "model": { "index": 3, "kind": "parameter", "displayName": "Model", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "jev-latest", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The model ID or alias. Use a versioned ID to pin decision behavior." }, - "questions": { "index": 4, "kind": "parameter", "displayName": "Questions", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "A JSON object mapping question names to Noul, Choice or Score question objects. When set, producers evalu [...] - "questionsResource": { "index": 5, "kind": "parameter", "displayName": "Questions Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Camel resource URI for a UTF-8 JSON object mapping question names to Noul, Choice or Sco [...] - "requestTimeout": { "index": 6, "kind": "parameter", "displayName": "Request Timeout", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The timeout in milliseconds for the complete HTTP request and response body. Must [...] - "state": { "index": 7, "kind": "parameter", "displayName": "State", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The Simple expression selecting state for configured producer questions and the TypeSafe AI language. If not set, [...] - "resultProperty": { "index": 8, "kind": "parameter", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Store the producer response in this exchange property, preserving the original message body." }, - "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] - "threshold": { "index": 10, "kind": "parameter", "displayName": "Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.5, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default inclusive probability threshold for the TypeSafe AI language. Must be within 0,1." }, - "uncertainty": { "index": 11, "kind": "parameter", "displayName": "Uncertainty", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.0, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default half-width of the inclusive uncertainty band for the TypeSafe AI language. Ze [...] - "uncertaintyPolicy": { "index": 12, "kind": "parameter", "displayName": "Uncertainty Policy", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.language.typesafeai.TypeSafeAiLanguage.UncertaintyPolicy", "enum": [ "NonMatch", "Fail" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NonMatch", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": " [...] - "apiKey": { "index": 13, "kind": "parameter", "displayName": "Api Key", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API key used for Bearer authentication." } + "apiPath": { "index": 1, "kind": "parameter", "displayName": "Api Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "\/v1\/systemone", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API path appended to baseUrl. A leading slash is optional. Must be a [...] + "baseUrl": { "index": 2, "kind": "parameter", "displayName": "Base Url", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "https:\/\/api.typesafe.ai", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API base URL. The client appends apiPath, preserving any ba [...] + "maxConcurrentRequests": { "index": 3, "kind": "parameter", "displayName": "Max Concurrent Requests", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 64, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Maximum concurrent evaluations per endpoint, shared by producers and pr [...] + "model": { "index": 4, "kind": "parameter", "displayName": "Model", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "jev-latest", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The model ID or alias. Use a versioned ID to pin decision behavior." }, + "questions": { "index": 5, "kind": "parameter", "displayName": "Questions", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "A JSON object mapping question names to Noul, Choice or Score question objects. When set, producers evalu [...] + "questionsResource": { "index": 6, "kind": "parameter", "displayName": "Questions Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Camel resource URI for a UTF-8 JSON object mapping question names to Noul, Choice or Sco [...] + "requestTimeout": { "index": 7, "kind": "parameter", "displayName": "Request Timeout", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The timeout in milliseconds for the complete HTTP request and response body. Must [...] + "state": { "index": 8, "kind": "parameter", "displayName": "State", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The Simple expression selecting state for configured producer questions and the TypeSafe AI language. If not set, [...] + "resultProperty": { "index": 9, "kind": "parameter", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Store the producer response in this exchange property, preserving the original message body." }, + "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] + "threshold": { "index": 11, "kind": "parameter", "displayName": "Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.5, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default inclusive probability threshold for the TypeSafe AI language. Must be within 0,1." }, + "uncertainty": { "index": 12, "kind": "parameter", "displayName": "Uncertainty", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.0, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default half-width of the inclusive uncertainty band for the TypeSafe AI language. Ze [...] + "uncertaintyPolicy": { "index": 13, "kind": "parameter", "displayName": "Uncertainty Policy", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.language.typesafeai.TypeSafeAiLanguage.UncertaintyPolicy", "enum": [ "NonMatch", "Fail" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NonMatch", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": " [...] + "apiKey": { "index": 14, "kind": "parameter", "displayName": "Api Key", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API key used for Bearer authentication." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/typesafe-ai-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/typesafe-ai-component.adoc index 2d1e0cd0ec58..942b64630924 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/typesafe-ai-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/typesafe-ai-component.adoc @@ -66,7 +66,8 @@ camel.component.typesafe-ai.max-concurrent-requests=64 ---- `apiKey` is a secret option. `baseUrl` defaults to `https://api.typesafe.ai`; the client -appends `/v1/systemone`. A base path can address a proxy. HTTP redirects are not followed. +appends `apiPath`, which defaults to `/v1/systemone`. A base path can address a proxy. +HTTP redirects are not followed. Use HTTPS for the service; HTTP is useful for local testing. `model` defaults to `jev-latest`. Pin a version when tuning thresholds; aliases can change @@ -74,6 +75,27 @@ behavior without a route change. The request map may explicitly override the end model for that request. The response's `model` identifies the model that answered. See https://docs.typesafe.ai/models[available models]. +=== Custom API path + +Set `apiPath` when a service or gateway exposes the supported TypeSafe AI request and +response schema at a different path: + +[source,properties] +---- +camel.component.typesafe-ai.base-url=http://localhost:8100 +camel.component.typesafe-ai.api-path=/v1/decision +---- + +The resulting request URL is `http://localhost:8100/v1/decision`. A path prefix in `baseUrl` +is preserved: `http://localhost:8100/gateway/` produces +`http://localhost:8100/gateway/v1/decision`. The leading slash in `apiPath` is optional. +The path must not be blank or include a scheme, authority, query or fragment. + +An endpoint can override the component setting with `typesafe-ai:refund?apiPath=/v1/decision`. +The Semantic language's TypeSafe AI adapter inherits the component setting even when the +route contains no `typesafe-ai` endpoint. Changing the path does not change the required +request and response schema. + === Concurrent requests `maxConcurrentRequests` limits concurrent evaluations to 64 per endpoint by default and must diff --git a/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiComponentConfigurer.java b/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiComponentConfigurer.java index 049cc2d4a171..dc15e479b3ce 100644 --- a/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiComponentConfigurer.java +++ b/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiComponentConfigurer.java @@ -32,6 +32,8 @@ public class TypeSafeAiComponentConfigurer extends PropertyConfigurerSupport imp switch (ignoreCase ? name.toLowerCase() : name) { case "apikey": case "apiKey": getOrCreateConfiguration(target).setApiKey(property(camelContext, java.lang.String.class, value)); return true; + case "apipath": + case "apiPath": getOrCreateConfiguration(target).setApiPath(property(camelContext, java.lang.String.class, value)); return true; case "autowiredenabled": case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; case "baseurl": @@ -63,6 +65,8 @@ public class TypeSafeAiComponentConfigurer extends PropertyConfigurerSupport imp switch (ignoreCase ? name.toLowerCase() : name) { case "apikey": case "apiKey": return java.lang.String.class; + case "apipath": + case "apiPath": return java.lang.String.class; case "autowiredenabled": case "autowiredEnabled": return boolean.class; case "baseurl": @@ -95,6 +99,8 @@ public class TypeSafeAiComponentConfigurer extends PropertyConfigurerSupport imp switch (ignoreCase ? name.toLowerCase() : name) { case "apikey": case "apiKey": return getOrCreateConfiguration(target).getApiKey(); + case "apipath": + case "apiPath": return getOrCreateConfiguration(target).getApiPath(); case "autowiredenabled": case "autowiredEnabled": return target.isAutowiredEnabled(); case "baseurl": diff --git a/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiEndpointConfigurer.java b/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiEndpointConfigurer.java index 463d1e8a473f..4b4d65371980 100644 --- a/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiEndpointConfigurer.java +++ b/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiEndpointConfigurer.java @@ -25,6 +25,8 @@ public class TypeSafeAiEndpointConfigurer extends PropertyConfigurerSupport impl switch (ignoreCase ? name.toLowerCase() : name) { case "apikey": case "apiKey": target.getConfiguration().setApiKey(property(camelContext, java.lang.String.class, value)); return true; + case "apipath": + case "apiPath": target.getConfiguration().setApiPath(property(camelContext, java.lang.String.class, value)); return true; case "baseurl": case "baseUrl": target.getConfiguration().setBaseUrl(property(camelContext, java.lang.String.class, value)); return true; case "lazystartproducer": @@ -53,6 +55,8 @@ public class TypeSafeAiEndpointConfigurer extends PropertyConfigurerSupport impl switch (ignoreCase ? name.toLowerCase() : name) { case "apikey": case "apiKey": return java.lang.String.class; + case "apipath": + case "apiPath": return java.lang.String.class; case "baseurl": case "baseUrl": return java.lang.String.class; case "lazystartproducer": @@ -82,6 +86,8 @@ public class TypeSafeAiEndpointConfigurer extends PropertyConfigurerSupport impl switch (ignoreCase ? name.toLowerCase() : name) { case "apikey": case "apiKey": return target.getConfiguration().getApiKey(); + case "apipath": + case "apiPath": return target.getConfiguration().getApiPath(); case "baseurl": case "baseUrl": return target.getConfiguration().getBaseUrl(); case "lazystartproducer": diff --git a/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiEndpointUriFactory.java b/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiEndpointUriFactory.java index f31f10cb2caa..b086097ca430 100644 --- a/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiEndpointUriFactory.java +++ b/components/camel-ai/camel-typesafe-ai/src/generated/java/org/apache/camel/component/typesafeai/TypeSafeAiEndpointUriFactory.java @@ -24,8 +24,9 @@ public class TypeSafeAiEndpointUriFactory extends org.apache.camel.support.compo private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES; private static final Map<String, String> MULTI_VALUE_PREFIXES; static { - Set<String> props = new HashSet<>(14); + Set<String> props = new HashSet<>(15); props.add("apiKey"); + props.add("apiPath"); props.add("baseUrl"); props.add("lazyStartProducer"); props.add("maxConcurrentRequests"); diff --git a/components/camel-ai/camel-typesafe-ai/src/generated/resources/META-INF/org/apache/camel/component/typesafeai/typesafe-ai.json b/components/camel-ai/camel-typesafe-ai/src/generated/resources/META-INF/org/apache/camel/component/typesafeai/typesafe-ai.json index 96a854019318..18c9bff96dd6 100644 --- a/components/camel-ai/camel-typesafe-ai/src/generated/resources/META-INF/org/apache/camel/component/typesafeai/typesafe-ai.json +++ b/components/camel-ai/camel-typesafe-ai/src/generated/resources/META-INF/org/apache/camel/component/typesafeai/typesafe-ai.json @@ -24,36 +24,38 @@ "remote": true }, "componentProperties": { - "baseUrl": { "index": 0, "kind": "property", "displayName": "Base Url", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "https:\/\/api.typesafe.ai", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API base URL. The client appends \/v1\/systemone. Redirects [...] - "configuration": { "index": 1, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Default configuration shared by TypeSafe AI endpoints." }, - "maxConcurrentRequests": { "index": 2, "kind": "property", "displayName": "Max Concurrent Requests", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 64, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Maximum concurrent evaluations per endpoint, shared by producers and pre [...] - "model": { "index": 3, "kind": "property", "displayName": "Model", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "jev-latest", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The model ID or alias. Use a versioned ID to pin decision behavior." }, - "questions": { "index": 4, "kind": "property", "displayName": "Questions", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "A JSON object mapping question names to Noul, Choice or Score question objects. When set, producers evalua [...] - "questionsResource": { "index": 5, "kind": "property", "displayName": "Questions Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Camel resource URI for a UTF-8 JSON object mapping question names to Noul, Choice or Scor [...] - "requestTimeout": { "index": 6, "kind": "property", "displayName": "Request Timeout", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The timeout in milliseconds for the complete HTTP request and response body. Must b [...] - "state": { "index": 7, "kind": "property", "displayName": "State", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The Simple expression selecting state for configured producer questions and the TypeSafe AI language. If not set, [...] - "lazyStartProducer": { "index": 8, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] - "resultProperty": { "index": 9, "kind": "property", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Store the producer response in this exchange property, preserving the original message body." }, - "autowiredEnabled": { "index": 10, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching [...] - "threshold": { "index": 11, "kind": "property", "displayName": "Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.5, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default inclusive probability threshold for the TypeSafe AI language. Must be within 0,1." }, - "uncertainty": { "index": 12, "kind": "property", "displayName": "Uncertainty", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.0, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default half-width of the inclusive uncertainty band for the TypeSafe AI language. Zer [...] - "uncertaintyPolicy": { "index": 13, "kind": "property", "displayName": "Uncertainty Policy", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.language.typesafeai.TypeSafeAiLanguage.UncertaintyPolicy", "enum": [ "NonMatch", "Fail" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NonMatch", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "c [...] - "apiKey": { "index": 14, "kind": "property", "displayName": "Api Key", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API key used for Bearer authentication." } + "apiPath": { "index": 0, "kind": "property", "displayName": "Api Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "\/v1\/systemone", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API path appended to baseUrl. A leading slash is optional. Must be a n [...] + "baseUrl": { "index": 1, "kind": "property", "displayName": "Base Url", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "https:\/\/api.typesafe.ai", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API base URL. The client appends apiPath, preserving any bas [...] + "configuration": { "index": 2, "kind": "property", "displayName": "Configuration", "group": "producer", "label": "", "required": false, "type": "object", "javaType": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "Default configuration shared by TypeSafe AI endpoints." }, + "maxConcurrentRequests": { "index": 3, "kind": "property", "displayName": "Max Concurrent Requests", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 64, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Maximum concurrent evaluations per endpoint, shared by producers and pre [...] + "model": { "index": 4, "kind": "property", "displayName": "Model", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "jev-latest", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The model ID or alias. Use a versioned ID to pin decision behavior." }, + "questions": { "index": 5, "kind": "property", "displayName": "Questions", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "A JSON object mapping question names to Noul, Choice or Score question objects. When set, producers evalua [...] + "questionsResource": { "index": 6, "kind": "property", "displayName": "Questions Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Camel resource URI for a UTF-8 JSON object mapping question names to Noul, Choice or Scor [...] + "requestTimeout": { "index": 7, "kind": "property", "displayName": "Request Timeout", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The timeout in milliseconds for the complete HTTP request and response body. Must b [...] + "state": { "index": 8, "kind": "property", "displayName": "State", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The Simple expression selecting state for configured producer questions and the TypeSafe AI language. If not set, [...] + "lazyStartProducer": { "index": 9, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] + "resultProperty": { "index": 10, "kind": "property", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Store the producer response in this exchange property, preserving the original message body." }, + "autowiredEnabled": { "index": 11, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching [...] + "threshold": { "index": 12, "kind": "property", "displayName": "Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.5, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default inclusive probability threshold for the TypeSafe AI language. Must be within 0,1." }, + "uncertainty": { "index": 13, "kind": "property", "displayName": "Uncertainty", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.0, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default half-width of the inclusive uncertainty band for the TypeSafe AI language. Zer [...] + "uncertaintyPolicy": { "index": 14, "kind": "property", "displayName": "Uncertainty Policy", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.language.typesafeai.TypeSafeAiLanguage.UncertaintyPolicy", "enum": [ "NonMatch", "Fail" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NonMatch", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "c [...] + "apiKey": { "index": 15, "kind": "property", "displayName": "Api Key", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API key used for Bearer authentication." } }, "properties": { "name": { "index": 0, "kind": "path", "displayName": "Name", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "A logical name for the evaluation endpoint." }, - "baseUrl": { "index": 1, "kind": "parameter", "displayName": "Base Url", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "https:\/\/api.typesafe.ai", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API base URL. The client appends \/v1\/systemone. Redirects [...] - "maxConcurrentRequests": { "index": 2, "kind": "parameter", "displayName": "Max Concurrent Requests", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 64, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Maximum concurrent evaluations per endpoint, shared by producers and pr [...] - "model": { "index": 3, "kind": "parameter", "displayName": "Model", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "jev-latest", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The model ID or alias. Use a versioned ID to pin decision behavior." }, - "questions": { "index": 4, "kind": "parameter", "displayName": "Questions", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "A JSON object mapping question names to Noul, Choice or Score question objects. When set, producers evalu [...] - "questionsResource": { "index": 5, "kind": "parameter", "displayName": "Questions Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Camel resource URI for a UTF-8 JSON object mapping question names to Noul, Choice or Sco [...] - "requestTimeout": { "index": 6, "kind": "parameter", "displayName": "Request Timeout", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The timeout in milliseconds for the complete HTTP request and response body. Must [...] - "state": { "index": 7, "kind": "parameter", "displayName": "State", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The Simple expression selecting state for configured producer questions and the TypeSafe AI language. If not set, [...] - "resultProperty": { "index": 8, "kind": "parameter", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Store the producer response in this exchange property, preserving the original message body." }, - "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produc [...] - "threshold": { "index": 10, "kind": "parameter", "displayName": "Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.5, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default inclusive probability threshold for the TypeSafe AI language. Must be within 0,1." }, - "uncertainty": { "index": 11, "kind": "parameter", "displayName": "Uncertainty", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.0, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default half-width of the inclusive uncertainty band for the TypeSafe AI language. Ze [...] - "uncertaintyPolicy": { "index": 12, "kind": "parameter", "displayName": "Uncertainty Policy", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.language.typesafeai.TypeSafeAiLanguage.UncertaintyPolicy", "enum": [ "NonMatch", "Fail" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NonMatch", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": " [...] - "apiKey": { "index": 13, "kind": "parameter", "displayName": "Api Key", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API key used for Bearer authentication." } + "apiPath": { "index": 1, "kind": "parameter", "displayName": "Api Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "\/v1\/systemone", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API path appended to baseUrl. A leading slash is optional. Must be a [...] + "baseUrl": { "index": 2, "kind": "parameter", "displayName": "Base Url", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "https:\/\/api.typesafe.ai", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API base URL. The client appends apiPath, preserving any ba [...] + "maxConcurrentRequests": { "index": 3, "kind": "parameter", "displayName": "Max Concurrent Requests", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 64, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Maximum concurrent evaluations per endpoint, shared by producers and pr [...] + "model": { "index": 4, "kind": "parameter", "displayName": "Model", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "jev-latest", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The model ID or alias. Use a versioned ID to pin decision behavior." }, + "questions": { "index": 5, "kind": "parameter", "displayName": "Questions", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "A JSON object mapping question names to Noul, Choice or Score question objects. When set, producers evalu [...] + "questionsResource": { "index": 6, "kind": "parameter", "displayName": "Questions Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Camel resource URI for a UTF-8 JSON object mapping question names to Noul, Choice or Sco [...] + "requestTimeout": { "index": 7, "kind": "parameter", "displayName": "Request Timeout", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The timeout in milliseconds for the complete HTTP request and response body. Must [...] + "state": { "index": 8, "kind": "parameter", "displayName": "State", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The Simple expression selecting state for configured producer questions and the TypeSafe AI language. If not set, [...] + "resultProperty": { "index": 9, "kind": "parameter", "displayName": "Result Property", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Store the producer response in this exchange property, preserving the original message body." }, + "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] + "threshold": { "index": 11, "kind": "parameter", "displayName": "Threshold", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.5, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default inclusive probability threshold for the TypeSafe AI language. Must be within 0,1." }, + "uncertainty": { "index": 12, "kind": "parameter", "displayName": "Uncertainty", "group": "advanced", "label": "advanced", "required": false, "type": "number", "javaType": "double", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0.0, "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "Default half-width of the inclusive uncertainty band for the TypeSafe AI language. Ze [...] + "uncertaintyPolicy": { "index": 13, "kind": "parameter", "displayName": "Uncertainty Policy", "group": "advanced", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.language.typesafeai.TypeSafeAiLanguage.UncertaintyPolicy", "enum": [ "NonMatch", "Fail" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NonMatch", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": " [...] + "apiKey": { "index": 14, "kind": "parameter", "displayName": "Api Key", "group": "security", "label": "security", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.typesafeai.TypeSafeAiConfiguration", "configurationField": "configuration", "description": "The API key used for Bearer authentication." } } } diff --git a/components/camel-ai/camel-typesafe-ai/src/main/docs/typesafe-ai-component.adoc b/components/camel-ai/camel-typesafe-ai/src/main/docs/typesafe-ai-component.adoc index 2d1e0cd0ec58..942b64630924 100644 --- a/components/camel-ai/camel-typesafe-ai/src/main/docs/typesafe-ai-component.adoc +++ b/components/camel-ai/camel-typesafe-ai/src/main/docs/typesafe-ai-component.adoc @@ -66,7 +66,8 @@ camel.component.typesafe-ai.max-concurrent-requests=64 ---- `apiKey` is a secret option. `baseUrl` defaults to `https://api.typesafe.ai`; the client -appends `/v1/systemone`. A base path can address a proxy. HTTP redirects are not followed. +appends `apiPath`, which defaults to `/v1/systemone`. A base path can address a proxy. +HTTP redirects are not followed. Use HTTPS for the service; HTTP is useful for local testing. `model` defaults to `jev-latest`. Pin a version when tuning thresholds; aliases can change @@ -74,6 +75,27 @@ behavior without a route change. The request map may explicitly override the end model for that request. The response's `model` identifies the model that answered. See https://docs.typesafe.ai/models[available models]. +=== Custom API path + +Set `apiPath` when a service or gateway exposes the supported TypeSafe AI request and +response schema at a different path: + +[source,properties] +---- +camel.component.typesafe-ai.base-url=http://localhost:8100 +camel.component.typesafe-ai.api-path=/v1/decision +---- + +The resulting request URL is `http://localhost:8100/v1/decision`. A path prefix in `baseUrl` +is preserved: `http://localhost:8100/gateway/` produces +`http://localhost:8100/gateway/v1/decision`. The leading slash in `apiPath` is optional. +The path must not be blank or include a scheme, authority, query or fragment. + +An endpoint can override the component setting with `typesafe-ai:refund?apiPath=/v1/decision`. +The Semantic language's TypeSafe AI adapter inherits the component setting even when the +route contains no `typesafe-ai` endpoint. Changing the path does not change the required +request and response schema. + === Concurrent requests `maxConcurrentRequests` limits concurrent evaluations to 64 per endpoint by default and must diff --git a/components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiClient.java b/components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiClient.java index 393d79053a23..8b4d3c4712cb 100644 --- a/components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiClient.java +++ b/components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiClient.java @@ -32,6 +32,7 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.apache.camel.util.URISupport; import org.apache.camel.util.json.JsonObject; import org.apache.camel.util.json.Jsoner; @@ -52,8 +53,7 @@ final class TypeSafeAiClient implements AutoCloseable { maxConcurrentRequests = configuration.getMaxConcurrentRequests(); apiKey = configuration.getApiKey(); model = configuration.getModel(); - String baseUrl = configuration.getBaseUrl(); - uri = URI.create((baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl) + "/v1/systemone"); + uri = URI.create(URISupport.joinPaths(configuration.getBaseUrl(), configuration.getApiPath())); http = HttpClient.newBuilder().connectTimeout(timeout).followRedirects(HttpClient.Redirect.NEVER).build(); } diff --git a/components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiConfiguration.java b/components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiConfiguration.java index b23be21886cc..9b814600fd82 100644 --- a/components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiConfiguration.java +++ b/components/camel-ai/camel-typesafe-ai/src/main/java/org/apache/camel/component/typesafeai/TypeSafeAiConfiguration.java @@ -31,6 +31,8 @@ public class TypeSafeAiConfiguration implements Cloneable { private String apiKey; @UriParam(label = "common", defaultValue = "https://api.typesafe.ai") private String baseUrl = "https://api.typesafe.ai"; + @UriParam(label = "common", defaultValue = "/v1/systemone") + private String apiPath = "/v1/systemone"; @UriParam(label = "common", defaultValue = "jev-latest") private String model = "jev-latest"; @UriParam(label = "common", defaultValue = "30000") @@ -147,11 +149,23 @@ public class TypeSafeAiConfiguration implements Cloneable { return baseUrl; } - /** The API base URL. The client appends /v1/systemone. Redirects are not followed. */ + /** The API base URL. The client appends apiPath, preserving any base path. Redirects are not followed. */ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } + public String getApiPath() { + return apiPath; + } + + /** + * The API path appended to baseUrl. A leading slash is optional. Must be a non-blank path without a scheme, + * authority, query or fragment. The service must implement the supported TypeSafe AI request and response schema. + */ + public void setApiPath(String apiPath) { + this.apiPath = apiPath; + } + public String getModel() { return model; } @@ -200,6 +214,7 @@ public class TypeSafeAiConfiguration implements Cloneable { ObjectHelper.notNull(apiKey, "apiKey"); ObjectHelper.notNull(model, "model"); ObjectHelper.notNull(baseUrl, "baseUrl"); + ObjectHelper.notNull(apiPath, "apiPath"); if (apiKey.isBlank() || model.isBlank()) { throw new IllegalArgumentException("apiKey and model must not be blank"); } @@ -211,6 +226,14 @@ public class TypeSafeAiConfiguration implements Cloneable { || uri.getHost() == null || uri.getUserInfo() != null || uri.getFragment() != null || uri.getQuery() != null) { throw new IllegalArgumentException("baseUrl must be an HTTP(S) URL without user information, query or fragment"); } + if (apiPath.isBlank()) { + throw new IllegalArgumentException("apiPath must not be blank"); + } + URI path = URI.create(apiPath); + if (path.getScheme() != null || path.getRawAuthority() != null || path.getQuery() != null + || path.getFragment() != null) { + throw new IllegalArgumentException("apiPath must be a path without a scheme, authority, query or fragment"); + } if (requestTimeout <= 0) { throw new IllegalArgumentException("requestTimeout must be positive"); } diff --git a/components/camel-ai/camel-typesafe-ai/src/test/java/org/apache/camel/component/typesafeai/TypeSafeAiApiPathTest.java b/components/camel-ai/camel-typesafe-ai/src/test/java/org/apache/camel/component/typesafeai/TypeSafeAiApiPathTest.java new file mode 100644 index 000000000000..38248cd7131a --- /dev/null +++ b/components/camel-ai/camel-typesafe-ai/src/test/java/org/apache/camel/component/typesafeai/TypeSafeAiApiPathTest.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.typesafeai; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.main.Main; +import org.apache.camel.semantic.SemanticQuestion; +import org.apache.camel.semantic.SemanticQuestions; +import org.apache.camel.util.json.JsonObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class TypeSafeAiApiPathTest extends TypeSafeAiTestSupport { + private final ConcurrentLinkedQueue<String> paths = new ConcurrentLinkedQueue<>(); + + @BeforeEach + void capturePaths() { + server.removeContext("/v1/systemone"); + server.createContext("/", exchange -> { + paths.add(exchange.getRequestURI().toString()); + handle(exchange); + }); + } + + @ParameterizedTest + @CsvSource({ + "'', /v1/systemone", "/, /v1/systemone", + "/gateway, /gateway/v1/systemone", "/gateway/, /gateway/v1/systemone", + "/gateway//, /gateway//v1/systemone" }) + void defaultPathPreservesBaseUrlBehavior(String prefix, String expectedPath) { + TypeSafeAiConfiguration configuration + = context.getComponent("typesafe-ai", TypeSafeAiComponent.class).getConfiguration(); + configuration.setBaseUrl(configuration.getBaseUrl() + prefix); + + JsonObject response = template.requestBody("typesafe-ai:default", request("Refund"), JsonObject.class); + + assertThat(response.getJsonObject("answers")).containsKey("predicate"); + assertThat(paths).containsExactly(expectedPath); + } + + @ParameterizedTest + @CsvSource({ + "'', /v1/decision, /v1/decision", "'', v1/decision, /v1/decision", + "/, /v1/decision, /v1/decision", "/, v1/decision, /v1/decision", + "/gateway, /v1/decision, /gateway/v1/decision", "/gateway, v1/decision, /gateway/v1/decision", + "/gateway/, /v1/decision, /gateway/v1/decision", "/gateway/, v1/decision, /gateway/v1/decision", + "/gateway/, /v1/decision/, /gateway/v1/decision/", "/gateway/, /, /gateway/" }) + void endpointPathOverridesComponentDefault(String prefix, String apiPath, String expectedPath) { + TypeSafeAiConfiguration configuration + = context.getComponent("typesafe-ai", TypeSafeAiComponent.class).getConfiguration(); + configuration.setBaseUrl(configuration.getBaseUrl() + prefix); + configuration.setApiPath("/component"); + + JsonObject response + = template.requestBody("typesafe-ai:custom?apiPath=" + apiPath, request("Refund"), JsonObject.class); + + assertThat(response.getJsonObject("answers")).containsKey("predicate"); + assertThat(paths).containsExactly(expectedPath); + assertThat(configuration.getApiPath()).isEqualTo("/component"); + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + void componentPropertiesConfigureProducerAndSemanticAdapter(boolean semantic) throws Exception { + Main main = new Main(); + main.addProperty("camel.component.typesafe-ai.api-key", "test-key"); + main.addProperty("camel.component.typesafe-ai.base-url", + "http://127.0.0.1:" + server.getAddress().getPort() + "/gateway/"); + main.addProperty("camel.component.typesafe-ai.api-path", "/v1/decision"); + main.configure().addRoutesBuilder(new RouteBuilder() { + @Override + public void configure() { + if (semantic) { + SemanticQuestions.get(getContext()).replace("test", Map.of("refund", new SemanticQuestion( + SemanticQuestion.Type.BOOLEAN, "Refund requested?", null, + Map.of(), List.of(), 0.5, 0, SemanticQuestion.UncertaintyPolicy.FAIL))); + from("direct:request").setBody().language("semantic", "ref:refund"); + } else { + from("direct:request").to("typesafe-ai:configured"); + } + } + }); + respond = request -> result(Map.of(semantic ? "question" : "predicate", Map.of("type", "noul", "noul", 0.9))); + try { + main.start(); + try (var producer = main.getCamelContext().createProducerTemplate()) { + Object response = producer.requestBody("direct:request", semantic ? "Refund" : request("Refund")); + if (semantic) { + assertThat(response).isEqualTo(true); + } else { + assertThat(((JsonObject) response).getJsonObject("answers")).containsKey("predicate"); + } + assertThat(paths).containsExactly("/gateway/v1/decision"); + assertThat(requests).hasSize(1); + assertThat(requests.peek().get("state")).isEqualTo("Refund"); + assertThat(authorization).containsExactly("Bearer test-key"); + } + } finally { + main.stop(); + } + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = { + " ", "https://example.com/v1/decision", "//example.com/v1/decision", "/v1/decision?query=value", + "/v1/decision#fragment", "/invalid path", "/invalid%path" }) + void rejectsInvalidPathsBeforeSending(String apiPath) { + context.getComponent("typesafe-ai", TypeSafeAiComponent.class).getConfiguration().setApiPath(apiPath); + + assertThatThrownBy(() -> context.getEndpoint("typesafe-ai:invalid")).isInstanceOf(Exception.class); + assertThat(paths).isEmpty(); + } +} diff --git a/components/camel-ai/camel-typesafe-ai/src/test/java/org/apache/camel/component/typesafeai/TypeSafeAiTestSupport.java b/components/camel-ai/camel-typesafe-ai/src/test/java/org/apache/camel/component/typesafeai/TypeSafeAiTestSupport.java index e32f630421e5..a282ee45dd3a 100644 --- a/components/camel-ai/camel-typesafe-ai/src/test/java/org/apache/camel/component/typesafeai/TypeSafeAiTestSupport.java +++ b/components/camel-ai/camel-typesafe-ai/src/test/java/org/apache/camel/component/typesafeai/TypeSafeAiTestSupport.java @@ -87,7 +87,7 @@ abstract class TypeSafeAiTestSupport { } } - private void handle(HttpExchange exchange) throws IOException { + void handle(HttpExchange exchange) throws IOException { try { JsonObject request = (JsonObject) Jsoner.deserialize( new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8)); diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/TypesafeAiComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/TypesafeAiComponentBuilderFactory.java index 0b906952c114..cec32d2b62f6 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/TypesafeAiComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/TypesafeAiComponentBuilderFactory.java @@ -52,8 +52,28 @@ public interface TypesafeAiComponentBuilderFactory { /** - * The API base URL. The client appends /v1/systemone. Redirects are not - * followed. + * The API path appended to baseUrl. A leading slash is optional. Must + * be a non-blank path without a scheme, authority, query or fragment. + * The service must implement the supported TypeSafe AI request and + * response schema. + * + * The option is a: <code>java.lang.String</code> type. + * + * Default: /v1/systemone + * Group: common + * + * @param apiPath the value to set + * @return the dsl builder + */ + default TypesafeAiComponentBuilder apiPath(java.lang.String apiPath) { + doSetProperty("apiPath", apiPath); + return this; + } + + + /** + * The API base URL. The client appends apiPath, preserving any base + * path. Redirects are not followed. * * The option is a: <code>java.lang.String</code> type. * @@ -341,6 +361,7 @@ public interface TypesafeAiComponentBuilderFactory { String name, Object value) { switch (name) { + case "apiPath": getOrCreateConfiguration((TypeSafeAiComponent) component).setApiPath((java.lang.String) value); return true; case "baseUrl": getOrCreateConfiguration((TypeSafeAiComponent) component).setBaseUrl((java.lang.String) value); return true; case "configuration": ((TypeSafeAiComponent) component).setConfiguration((org.apache.camel.component.typesafeai.TypeSafeAiConfiguration) value); return true; case "maxConcurrentRequests": getOrCreateConfiguration((TypeSafeAiComponent) component).setMaxConcurrentRequests((int) value); return true; diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TypeSafeAiEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TypeSafeAiEndpointBuilderFactory.java index 3ed41187a75d..d43faded2df3 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TypeSafeAiEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/TypeSafeAiEndpointBuilderFactory.java @@ -45,8 +45,26 @@ public interface TypeSafeAiEndpointBuilderFactory { } /** - * The API base URL. The client appends /v1/systemone. Redirects are not - * followed. + * The API path appended to baseUrl. A leading slash is optional. Must + * be a non-blank path without a scheme, authority, query or fragment. + * The service must implement the supported TypeSafe AI request and + * response schema. + * + * The option is a: <code>java.lang.String</code> type. + * + * Default: /v1/systemone + * Group: common + * + * @param apiPath the value to set + * @return the dsl builder + */ + default TypeSafeAiEndpointBuilder apiPath(String apiPath) { + doSetProperty("apiPath", apiPath); + return this; + } + /** + * The API base URL. The client appends apiPath, preserving any base + * path. Redirects are not followed. * * The option is a: <code>java.lang.String</code> type. *
