Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Don't include in releases.
.docker/ export-ignore
.dockerignore export-ignore
docker-compose.yml export-ignore
Makefile export-ignore
tests/ export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.github/ export-ignore
.gitlab-ci.yml export-ignore
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
vendor/
.idea/
.vscode/
.claude/
.DS_Store
*.log
339 changes: 339 additions & 0 deletions LICENSE.txt

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions ai_provider_quant_cloud.info.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: 'Quant Cloud AI Provider'
description: 'AI provider for Quant Cloud platform (QuantCDN/QuantGov). Provides chat and embeddings via Dashboard API with OAuth authentication.'
type: module
package: 'AI'
package: 'AI Providers'
lifecycle: experimental
lifecycle_link: 'https://www.drupal.org/project/ai_provider_quant_cloud'
core_version_requirement: ^10.3 || ^11 || ^12
configure: ai_provider_quant_cloud.settings_form

dependencies:
- ai:ai
- key:key

13 changes: 13 additions & 0 deletions ai_provider_quant_cloud.install
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,16 @@ function ai_provider_quant_cloud_update_10002(): void {
$config->set('model.max_tokens', 16384)->save(TRUE);
}
}

/**
* Raise default max tokens from 16384 to 32768 for richer tool-use chains.
*/
function ai_provider_quant_cloud_update_10003(): void {
$config = \Drupal::configFactory()
->getEditable('ai_provider_quant_cloud.settings');

$max_tokens = (int) $config->get('model.max_tokens');
if ($max_tokens <= 16384) {
$config->set('model.max_tokens', 32768)->save(TRUE);
}
}
1 change: 0 additions & 1 deletion ai_provider_quant_cloud.links.menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ ai_provider_quant_cloud.settings:
route_name: ai_provider_quant_cloud.settings_form
parent: ai.admin_providers
weight: 10

11 changes: 5 additions & 6 deletions ai_provider_quant_cloud.module
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function ai_provider_quant_cloud_help($route_name, RouteMatchInterface $route_ma
case 'help.page.ai_provider_quant_cloud':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Quant Cloud AI Provider module enables integration with Quant Cloud AI services, providing access to AWS Bedrock models through the Quant Cloud platform.') . '</p>';

$output .= '<h3>' . t('Features') . '</h3>';
$output .= '<ul>';
$output .= '<li>' . t('<strong>Chat Completions:</strong> Multi-turn conversations with Claude and Nova models') . '</li>';
Expand All @@ -25,28 +25,27 @@ function ai_provider_quant_cloud_help($route_name, RouteMatchInterface $route_ma
$output .= '<li>' . t('<strong>Multi-Tenant:</strong> Organization-level access control') . '</li>';
$output .= '<li>' . t('<strong>Government-Ready:</strong> Designed for compliance with Australian government standards') . '</li>';
$output .= '</ul>';

$output .= '<h3>' . t('Platform Support') . '</h3>';
$output .= '<p>' . t('This provider works with both:') . '</p>';
$output .= '<ul>';
$output .= '<li>' . t('<strong>QuantCDN:</strong> https://dashboard.quantcdn.io') . '</li>';
$output .= '<li>' . t('<strong>QuantGov Cloud:</strong> https://dash.quantgov.cloud') . '</li>';
$output .= '</ul>';

$output .= '<h3>' . t('Configuration') . '</h3>';
$output .= '<p>' . t('Configure the provider at <a href=":url">Administration > Configuration > AI > Quant Cloud AI Settings</a>.', [
':url' => \Drupal::url('ai_provider_quant_cloud.settings_form'),
]) . '</p>';

$output .= '<h3>' . t('Authentication') . '</h3>';
$output .= '<p>' . t('The module supports OAuth2 authentication through the Quant Cloud dashboard. You will need:') . '</p>';
$output .= '<ul>';
$output .= '<li>' . t('An active Quant Cloud account') . '</li>';
$output .= '<li>' . t('API access token from your dashboard') . '</li>';
$output .= '<li>' . t('Organization ID') . '</li>';
$output .= '</ul>';

return $output;
}
}

7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
"require": {
"php": ">=8.2",
"drupal/core": "^10.3 || ^11 || ^12",
"drupal/ai": "^1.0",
"drupal/key": "^1.17"
"drupal/ai": "^1.2.0",
"drupal/key": "^1.17",
"drupal/search_api": "^1.36"
},
"require-dev": {
"drupal/core-dev": "^10.3 || ^11 || ^12"
},
"minimum-stability": "dev",
"minimum-stability": "stable",
"prefer-stable": true
}

2 changes: 1 addition & 1 deletion config/install/ai_provider_quant_cloud.settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ auth:
model:
default: 'amazon.nova-lite-v1:0'
temperature: 0.7
max_tokens: 16384
max_tokens: 32768

# Advanced request settings.
advanced:
Expand Down
2 changes: 0 additions & 2 deletions config/schema/ai_provider_quant_cloud.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,3 @@ ai_provider.plugin.quant_cloud:
type: string
organization_id:
type: string


2 changes: 1 addition & 1 deletion definitions/api_defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ chat:
label: 'Max Tokens'
description: 'The maximum number of tokens to generate. Supported upper limits vary by model.'
type: 'integer'
default: 16384
default: 32768
required: false
constraints:
min: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,8 +445,17 @@ public function insertIntoCollection(
$response = $this->vdbClient->uploadDocuments($collection_id, $documents);

// Store ID mapping in state for later retrieval.
$document_ids = $response['documentIds'] ?? [];
if (!empty($document_ids) && !empty($data['drupal_long_id'])) {
$document_ids = $response['uploadedDocuments'][0]['documentIds'] ?? [];
if (empty($document_ids)) {
// Upload returned 2xx but no document IDs — treat as a partial
// failure so the operator can investigate rather than silently
// continuing with a missing id-mapping entry.
$this->getLogger('ai_provider_quant_cloud')->warning(
'Document upload to collection @collection returned no document IDs. Response shape may have changed; mapping not stored.',
['@collection' => $collection_name],
);
}
elseif (!empty($data['drupal_long_id'])) {
$state_key = "ai_provider_quant_cloud.vdb_mapping.{$collection_name}";
$mapping = \Drupal::state()->get($state_key, []);
$mapping[$data['drupal_long_id']] = $document_ids[0];
Expand Down Expand Up @@ -620,22 +629,24 @@ public function vectorSearch(
$vector,
$limit,
0.0,
TRUE // Include metadata
// Include metadata.
TRUE
);

// Map API response to expected format.
// Drupal's SearchApiAiSearchBackend expects:
// - 'distance' for score (used by setScore(), skipped by extractMetadata())
// - 'drupal_entity_id' for entity lookup
// - 'id' for the full chunk ID (skipped by extractMetadata())
// - 'content' is added to extra data for display
// - 'content' is added to extra data for display.
$results = [];
foreach ($response['results'] ?? [] as $result) {
$metadata = $result['metadata'] ?? [];
$results[] = [
'id' => $metadata['drupal_long_id'] ?? $result['documentId'],
'drupal_entity_id' => $metadata['drupal_entity_id'] ?? NULL,
'distance' => $result['score'] ?? 0.0, // Backend expects 'distance', not 'score'
// Backend expects 'distance', not 'score'.
'distance' => $result['score'] ?? 0.0,
'content' => $result['content'] ?? '',
];
}
Expand Down
Loading