Skip to content

Commit 7ebc38f

Browse files
dzehnderclaude
andauthored
feat: trigger DRS prompt generation during LLMO onboarding (#1781)
## Summary Adds a DRS client to trigger prompt generation jobs during LLMO onboarding (LLMO-2656) and defers `llmo-customer-analysis` to DRS completion (LLMO-1819). - **DRS Client** (`src/support/drs-client.js`): HTTP client for submitting prompt generation jobs to DRS - Submits jobs with provider `prompt_generation_base_url` - Passes site metadata (site_id, imsOrgId, brand, base_url, region) and `source: 'onboarding'` - Fetches target audience from the site's brand profile, falls back to a default if unavailable - Non-blocking: onboarding completes successfully even if DRS submission fails - **Onboarding Integration** (`src/controllers/llmo/llmo-onboarding.js`): Calls DRS after all critical onboarding steps complete (org/site creation, entitlement, SharePoint, audits). No longer triggers `llmo-customer-analysis` directly — that is now triggered by the audit-worker when DRS completes. ### Full flow ``` api-service onboarding → submit DRS job (source: 'onboarding', site_id, brand, etc.) → DRS processes → SNS JOB_COMPLETED (presigned URL) → audit-worker: fetch → write JSON + parquet → trigger llmo-customer-analysis ``` ### Environment Variables Required ``` DRS_API_URL=https://drs-api.example.com/api/v1 DRS_API_KEY=<from-ssm> ``` ## Related PRs - spacecat-audit-worker #2041: handler for DRS notifications + parquet conversion - spacecat-infrastructure #346: SNS → SQS subscription - llmo-data-retrieval-service #756: add `source` field + presigned URL in notifications ## Test plan - [x] Unit tests for DRS client (7 tests) - [x] Unit tests for onboarding integration with DRS (3 tests) - [x] 100% branch coverage on changed files - [ ] E2E: onboarding → DRS job created → completes → audit-worker triggers llmo-customer-analysis 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7aec710 commit 7ebc38f

File tree

7 files changed

+570
-4
lines changed

7 files changed

+570
-4
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"build": "hedy -v --test-bundle",
2323
"deploy": "hedy -v --deploy --aws-deploy-bucket=spacecat-prod-deploy --pkgVersion=latest",
2424
"deploy-stage": "hedy -v --deploy --aws-deploy-bucket=spacecat-stage-deploy --pkgVersion=latest",
25-
"deploy-dev": "hedy -v --deploy --pkgVersion=latest$CI_BUILD_NUM -l latest --aws-deploy-bucket=spacecat-dev-deploy --cleanup-ci=24h",
25+
"deploy-dev": "hedy -v --deploy --pkgVersion=latest$CI_BUILD_NUM -l dzehnder --aws-deploy-bucket=spacecat-dev-deploy --cleanup-ci=24h",
2626
"deploy-secrets": "hedy --aws-update-secrets --params-file=secrets/secrets.env",
2727
"docs": "npm run docs:lint && npm run docs:build",
2828
"docs:build": "npx @redocly/cli build-docs -o ./docs/index.html --config docs/openapi/redocly-config.yaml",

src/controllers/hooks.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import { isHelixSite } from '../support/utils.js';
3333
import { getHlxConfigMessagePart } from '../utils/slack/base.js';
3434

3535
const CDN_HOOK_SECRET_NAME = 'INCOMING_WEBHOOK_SECRET_CDN';
36-
3736
export const BUTTON_LABELS = {
3837
APPROVE_CUSTOMER: 'As Customer',
3938
APPROVE_FRIENDS_FAMILY: 'As Friends/Family',

src/controllers/llmo/llmo-onboarding.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { composeBaseURL, tracingFetch as fetch, isNonEmptyArray } from '@adobe/s
1919
import AhrefsAPIClient from '@adobe/spacecat-shared-ahrefs-client';
2020
import { parse as parseDomain } from 'tldts';
2121
import { postSlackMessage } from '../../utils/slack/base.js';
22+
import DrsClient from '../../support/drs-client.js';
2223

2324
// LLMO Constants
2425
const LLMO_PRODUCT_CODE = EntitlementModel.PRODUCT_CODES.LLMO;
@@ -1056,8 +1057,37 @@ export async function performLlmoOnboarding(params, context, say = () => {}) {
10561057
site.setConfig(Config.toDynamoItem(siteConfig));
10571058
await site.save();
10581059

1059-
// Trigger audits
1060-
await triggerAudits([...BASIC_AUDITS, 'llmo-customer-analysis', 'wikipedia-analysis'], context, site);
1060+
// Trigger audits (llmo-customer-analysis is NOT triggered here; it will be triggered
1061+
// after the DRS prompt generation job completes, via SNS → audit-worker. LLMO-1819)
1062+
await triggerAudits([...BASIC_AUDITS, 'wikipedia-analysis'], context, site);
1063+
1064+
// Submit DRS prompt generation job (non-blocking)
1065+
try {
1066+
const drsClient = DrsClient(context);
1067+
if (drsClient.isConfigured()) {
1068+
// Try to get audience from brand profile, fall back to default
1069+
const brandProfile = siteConfig.getBrandProfile?.();
1070+
const audience = brandProfile?.main_profile?.target_audience
1071+
|| `General consumers interested in ${brandName} products and services`;
1072+
1073+
const drsJob = await drsClient.submitPromptGenerationJob({
1074+
baseUrl: baseURL,
1075+
brandName: brandName.trim(),
1076+
audience,
1077+
region: 'US',
1078+
numPrompts: 42,
1079+
siteId: site.getId(),
1080+
imsOrgId,
1081+
});
1082+
log.info(`Started DRS prompt generation: job=${drsJob.job_id}`);
1083+
say(`:robot_face: Started DRS prompt generation job: ${drsJob.job_id}`);
1084+
} else {
1085+
log.debug('DRS client not configured, skipping prompt generation');
1086+
}
1087+
} catch (drsError) {
1088+
log.error(`Failed to start DRS prompt generation: ${drsError.message}`);
1089+
say(':warning: Failed to start DRS prompt generation (will need manual trigger)');
1090+
}
10611091

10621092
return {
10631093
site,

src/support/drs-client.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2026 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import { tracingFetch as fetch } from '@adobe/spacecat-shared-utils';
14+
15+
/**
16+
* Client for interacting with the Data Retrieval Service (DRS) API.
17+
* Used to submit prompt generation jobs for LLMO onboarding.
18+
*
19+
* @param {object} context - The request context
20+
* @returns {object} DRS client with methods for submitting jobs
21+
*/
22+
export default function DrsClient(context) {
23+
const { env, log } = context;
24+
const drsApiUrl = env.DRS_API_URL;
25+
const drsApiKey = env.DRS_API_KEY;
26+
27+
/**
28+
* Checks if the DRS client is properly configured.
29+
* @returns {boolean} True if DRS_API_URL and DRS_API_KEY are set
30+
*/
31+
function isConfigured() {
32+
return Boolean(drsApiUrl && drsApiKey);
33+
}
34+
35+
/**
36+
* Submits a prompt generation job to DRS.
37+
*
38+
* @param {object} params - Job parameters
39+
* @param {string} params.baseUrl - The base URL of the site
40+
* @param {string} params.brandName - The brand name
41+
* @param {string} params.audience - Target audience description
42+
* @param {string} [params.region='US'] - Geographic region for prompts
43+
* @param {number} [params.numPrompts=42] - Number of prompts to generate
44+
* @param {string} params.siteId - The SpaceCat site ID
45+
* @param {string} params.imsOrgId - The Adobe IMS organization ID
46+
* @returns {Promise<object>} Job submission result with job_id
47+
* @throws {Error} If job submission fails or DRS is not configured
48+
*/
49+
async function submitPromptGenerationJob({
50+
baseUrl,
51+
brandName,
52+
audience,
53+
region = 'US',
54+
numPrompts = 42,
55+
siteId,
56+
imsOrgId,
57+
}) {
58+
if (!isConfigured()) {
59+
throw new Error('DRS client is not configured. Set DRS_API_URL and DRS_API_KEY environment variables.');
60+
}
61+
62+
const payload = {
63+
provider_id: 'prompt_generation_base_url',
64+
source: 'onboarding',
65+
parameters: {
66+
base_url: baseUrl,
67+
brand_name: brandName,
68+
audience,
69+
region,
70+
num_prompts: numPrompts,
71+
model: 'gpt-5-nano',
72+
metadata: {
73+
site_id: siteId,
74+
imsOrgId,
75+
base_url: baseUrl,
76+
brand: brandName,
77+
region,
78+
},
79+
},
80+
};
81+
82+
log.info(`Submitting DRS prompt generation job for site ${siteId}`, {
83+
baseUrl,
84+
brandName,
85+
region,
86+
numPrompts,
87+
});
88+
89+
const response = await fetch(`${drsApiUrl}/jobs`, {
90+
method: 'POST',
91+
headers: {
92+
'Content-Type': 'application/json',
93+
'x-api-key': drsApiKey,
94+
},
95+
body: JSON.stringify(payload),
96+
});
97+
98+
if (!response.ok) {
99+
const errorText = await response.text();
100+
throw new Error(`DRS job submission failed: ${response.status} - ${errorText}`);
101+
}
102+
103+
const result = await response.json();
104+
log.info(`DRS job submitted successfully: ${result.job_id}`, {
105+
siteId,
106+
jobId: result.job_id,
107+
});
108+
109+
return result;
110+
}
111+
112+
return {
113+
isConfigured,
114+
submitPromptGenerationJob,
115+
};
116+
}

0 commit comments

Comments
 (0)