@alvera-ai/platform-sdk 0.10.0-rc.20 → 0.10.0-rc.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/ai_agents.md +5 -5
- package/.agent/connected_apps.md +12 -7
- package/.agent/cookbook/appointment-review-sms-workflow.md +8 -7
- package/.agent/cookbook/birthday-greeting-sms-trigger.md +8 -7
- package/.agent/cookbook/contact-us-triage-with-llm.md +5 -5
- package/.agent/cookbook/dunning-sms-for-delinquent.md +5 -5
- package/.agent/cookbook/kyc-notification-on-account-activation.md +5 -5
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +5 -5
- package/.agent/cookbook/score-leads-with-llm-categorization.md +5 -5
- package/.agent/cookbook/welcome-sms-for-customers.md +5 -5
- package/.agent/tools.md +6 -7
- package/dist/index.d.mts +708 -122
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +649 -378
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/.agent/ai_agents.md
CHANGED
|
@@ -28,7 +28,7 @@ const { data: created } = await api.aiAgents.create(
|
|
|
28
28
|
name: 'Contact Us Triage Categorizer',
|
|
29
29
|
description: 'Triages inbound submissions into appointment / job / spam',
|
|
30
30
|
|
|
31
|
-
tool_id: chatCompletionToolId, // see §2 — must be a tool with intent: '
|
|
31
|
+
tool_id: chatCompletionToolId, // see §2 — must be a tool with intent: 'llm_enrichment'
|
|
32
32
|
|
|
33
33
|
model: 'llama3.2:3b', // provider-specific model identifier
|
|
34
34
|
data_access: 'unregulated', // 'unregulated' | 'regulated' (compliance gate)
|
|
@@ -76,7 +76,7 @@ Respond with JSON: {"category": "..."}`,
|
|
|
76
76
|
|
|
77
77
|
### `tool_id` must point to a chat-completion tool
|
|
78
78
|
|
|
79
|
-
The bound tool's `intent` MUST be `
|
|
79
|
+
The bound tool's `intent` MUST be `llm_enrichment`. Tools with
|
|
80
80
|
any other intent (`sms`, `data_exchange`, `status_poller`, …)
|
|
81
81
|
are rejected at create time as a 422 on `/tool_id`.
|
|
82
82
|
|
|
@@ -256,7 +256,7 @@ reference via `additional_context.<slug>`.
|
|
|
256
256
|
```
|
|
257
257
|
name required string
|
|
258
258
|
description optional string
|
|
259
|
-
tool_id required UUID — must be intent:
|
|
259
|
+
tool_id required UUID — must be intent: llm_enrichment
|
|
260
260
|
model required string — provider-specific identifier
|
|
261
261
|
data_access required enum — 'unregulated' | 'regulated'
|
|
262
262
|
temperature required number
|
|
@@ -275,7 +275,7 @@ Standard JSON:API envelopes per `errors.md`. Common rejections:
|
|
|
275
275
|
|
|
276
276
|
| `source.pointer` | Cause |
|
|
277
277
|
|---------------------------------|----------------------------------------------------|
|
|
278
|
-
| `/tool_id` | tool's intent isn't `
|
|
278
|
+
| `/tool_id` | tool's intent isn't `llm_enrichment` |
|
|
279
279
|
| `/llm_response_schema` | missing or not a valid JSON Schema object |
|
|
280
280
|
| `/input_schema` | missing or not a valid JSON Schema object |
|
|
281
281
|
| `/prompt_config/body` | references a field absent from `input_schema` |
|
|
@@ -390,7 +390,7 @@ with `{ status: 'skipped' }` and no per-agent entries.
|
|
|
390
390
|
the schema means no structured output and downstream decision
|
|
391
391
|
templates can't reference the agent's fields.
|
|
392
392
|
|
|
393
|
-
2. **`tool_id` must reference an `intent: '
|
|
393
|
+
2. **`tool_id` must reference an `intent: 'llm_enrichment'`
|
|
394
394
|
tool.** Other intents are rejected at create. The chat
|
|
395
395
|
completion tool is typically `tool_body_type: 'rest_api'`
|
|
396
396
|
against an OpenAI-compatible endpoint; the platform appends
|
package/.agent/connected_apps.md
CHANGED
|
@@ -229,17 +229,22 @@ and can be retried via `.syncRoutes`.
|
|
|
229
229
|
### Sync routes
|
|
230
230
|
|
|
231
231
|
```typescript
|
|
232
|
-
const { data:
|
|
232
|
+
const { data: ack } = await api.connectedApps.syncRoutes(
|
|
233
233
|
tenantSlug, datalakeSlug, appId,
|
|
234
234
|
)
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
235
|
+
// 202 Accepted — the sync runs in a background worker:
|
|
236
|
+
// ack.status — 'enqueued'
|
|
237
|
+
// ack.job_id — background job id
|
|
238
|
+
// ack.connected_app_id — echo of the app id
|
|
239
|
+
// ack.enqueued_at — enqueue timestamp
|
|
238
240
|
```
|
|
239
241
|
|
|
240
|
-
This is the explicit "re-fetch and save" verb
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
This is the explicit "re-fetch and save" verb — asynchronous. The
|
|
243
|
+
202 acknowledgement proves the app id resolved and a sync job was
|
|
244
|
+
accepted; the worker then refreshes `routes` + `last_synced_at` and
|
|
245
|
+
lands `status: 'synced'` (or `'error'`) on the app row. Poll
|
|
246
|
+
`.get()` for the outcome. Use it after deploying a new manifest,
|
|
247
|
+
fixing a primary URL outage, or recovering from `status: 'error'`.
|
|
243
248
|
|
|
244
249
|
Discovery failure modes the platform handles explicitly — each
|
|
245
250
|
leaves the row at `status: 'error'` with `error` populated:
|
|
@@ -482,7 +482,7 @@ leaves `:pending`. A `:partial` status is expected and fine here:
|
|
|
482
482
|
one row passed and one was filtered.
|
|
483
483
|
|
|
484
484
|
```typescript
|
|
485
|
-
const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
|
|
485
|
+
const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
486
486
|
sql_where_clause: `ra.batch_id IN ('${ctx.batchFulfilled}', '${ctx.batchCancelled}')`,
|
|
487
487
|
mode: 'live',
|
|
488
488
|
manual_override: false,
|
|
@@ -493,7 +493,7 @@ ctx.runBatchId = runResp.data.batch_id!
|
|
|
493
493
|
const deadline = Date.now() + 120_000
|
|
494
494
|
let status: string | null = null
|
|
495
495
|
while (Date.now() < deadline) {
|
|
496
|
-
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
|
|
496
|
+
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
|
|
497
497
|
status = log.status ?? null
|
|
498
498
|
if (status && status !== 'pending') break
|
|
499
499
|
await new Promise((r) => setTimeout(r, 2_000))
|
|
@@ -514,7 +514,7 @@ filtered — would mean the filter is not actually evaluating the
|
|
|
514
514
|
appointment status.
|
|
515
515
|
|
|
516
516
|
```typescript
|
|
517
|
-
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
|
|
517
|
+
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
|
|
518
518
|
const ourWels = (wfLogs.data ?? []).filter(
|
|
519
519
|
(w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
|
|
520
520
|
)
|
|
@@ -604,7 +604,7 @@ if (!fulfilledAppointmentId) {
|
|
|
604
604
|
throw new Error('fulfilled appointment not found via unregulated search')
|
|
605
605
|
}
|
|
606
606
|
|
|
607
|
-
const { data: execResp } = await api.workflows.execute(tenantSlug, ctx.workflowSlug, {
|
|
607
|
+
const { data: execResp } = await api.workflows.execute(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
608
608
|
dataset_id: fulfilledAppointmentId,
|
|
609
609
|
decision_key: 'send_appointment_review_sms',
|
|
610
610
|
manual_override: true,
|
|
@@ -614,7 +614,7 @@ ctx.execWelId = execResp.workflow_execution_log_id!
|
|
|
614
614
|
const deadline = Date.now() + 60_000
|
|
615
615
|
let welStatus: string | null = null
|
|
616
616
|
while (Date.now() < deadline) {
|
|
617
|
-
const { data: wel } = await api.workflows.workflowLogs.get(tenantSlug, ctx.workflowSlug, ctx.execWelId)
|
|
617
|
+
const { data: wel } = await api.workflows.workflowLogs.get(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.execWelId)
|
|
618
618
|
welStatus = (wel as { status?: string }).status ?? null
|
|
619
619
|
if (welStatus && welStatus !== 'pending' && welStatus !== 'executing') break
|
|
620
620
|
await new Promise((r) => setTimeout(r, 2_000))
|
|
@@ -639,6 +639,7 @@ closing the SMS → reply loop end-to-end.
|
|
|
639
639
|
```typescript
|
|
640
640
|
const { data: regulatedWel } = await api.workflows.workflowLogs.get(
|
|
641
641
|
tenantSlug,
|
|
642
|
+
datalakeSlug,
|
|
642
643
|
ctx.workflowSlug,
|
|
643
644
|
ctx.execWelId,
|
|
644
645
|
{ dataAccessMode: 'regulated' },
|
|
@@ -655,7 +656,7 @@ if (!tokenMatch) {
|
|
|
655
656
|
}
|
|
656
657
|
const shortPath = tokenMatch[1]!
|
|
657
658
|
|
|
658
|
-
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
|
|
659
|
+
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
659
660
|
short_path: shortPath,
|
|
660
661
|
user_agent: 'cookbook-doctest/appointment-review',
|
|
661
662
|
})
|
|
@@ -664,7 +665,7 @@ if (resolved.route_path !== '/forms/review') {
|
|
|
664
665
|
}
|
|
665
666
|
|
|
666
667
|
const now = new Date().toISOString()
|
|
667
|
-
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
|
|
668
|
+
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
668
669
|
short_path: shortPath,
|
|
669
670
|
opened_at: now,
|
|
670
671
|
form_submitted_at: now,
|
|
@@ -393,7 +393,7 @@ life of the run. So this step polls on `total_wels` (every per-row job
|
|
|
393
393
|
has produced a WEL) rather than waiting for the run to conclude.
|
|
394
394
|
|
|
395
395
|
```typescript
|
|
396
|
-
const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
|
|
396
|
+
const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
397
397
|
sql_where_clause: `rle.batch_id IN ('${ctx.batchBirthday}', '${ctx.batchNoDob}')`,
|
|
398
398
|
mode: 'live',
|
|
399
399
|
manual_override: false,
|
|
@@ -404,7 +404,7 @@ ctx.runBatchId = runResp.data.batch_id!
|
|
|
404
404
|
const deadline = Date.now() + 60_000
|
|
405
405
|
let totalWels = 0
|
|
406
406
|
while (Date.now() < deadline) {
|
|
407
|
-
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
|
|
407
|
+
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
|
|
408
408
|
if (log.status === 'failed') throw new Error('workflow run reached :failed')
|
|
409
409
|
totalWels = typeof log.total_wels === 'number' ? log.total_wels : 0
|
|
410
410
|
if (totalWels >= 2) break
|
|
@@ -424,7 +424,7 @@ old shallow version of this cookbook could never catch that because
|
|
|
424
424
|
it never ran the workflow at all.
|
|
425
425
|
|
|
426
426
|
```typescript
|
|
427
|
-
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
|
|
427
|
+
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
|
|
428
428
|
const ourWels = (wfLogs.data ?? []).filter(
|
|
429
429
|
(w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
|
|
430
430
|
)
|
|
@@ -475,7 +475,7 @@ const { data: search } = await api.datasets.search(tenantSlug, datalakeSlug, 'le
|
|
|
475
475
|
const birthdayLegalEntityId = (search.data?.[0] as { id?: string } | undefined)?.id
|
|
476
476
|
if (!birthdayLegalEntityId) throw new Error('birthday row legal_entity not found via search')
|
|
477
477
|
|
|
478
|
-
const { data: execResp } = await api.workflows.execute(tenantSlug, ctx.workflowSlug, {
|
|
478
|
+
const { data: execResp } = await api.workflows.execute(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
479
479
|
dataset_id: birthdayLegalEntityId,
|
|
480
480
|
decision_key: 'send_happy_birthday_sms',
|
|
481
481
|
manual_override: true,
|
|
@@ -486,7 +486,7 @@ ctx.forceExecWelId = execResp.workflow_execution_log_id!
|
|
|
486
486
|
const deadline = Date.now() + 60_000
|
|
487
487
|
let welStatus: string | null = null
|
|
488
488
|
while (Date.now() < deadline) {
|
|
489
|
-
const { data: wel } = await api.workflows.workflowLogs.get(tenantSlug, ctx.workflowSlug, ctx.forceExecWelId)
|
|
489
|
+
const { data: wel } = await api.workflows.workflowLogs.get(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.forceExecWelId)
|
|
490
490
|
welStatus = (wel as { status?: string }).status ?? null
|
|
491
491
|
if (welStatus && welStatus !== 'pending' && welStatus !== 'executing') break
|
|
492
492
|
await new Promise((r) => setTimeout(r, 2_000))
|
|
@@ -541,6 +541,7 @@ the page, closing the SMS → reply loop end-to-end.
|
|
|
541
541
|
```typescript
|
|
542
542
|
const { data: regulatedWel } = await api.workflows.workflowLogs.get(
|
|
543
543
|
tenantSlug,
|
|
544
|
+
datalakeSlug,
|
|
544
545
|
ctx.workflowSlug,
|
|
545
546
|
ctx.forceExecWelId,
|
|
546
547
|
{ dataAccessMode: 'regulated' },
|
|
@@ -557,7 +558,7 @@ if (!tokenMatch) {
|
|
|
557
558
|
}
|
|
558
559
|
const shortPath = tokenMatch[1]!
|
|
559
560
|
|
|
560
|
-
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
|
|
561
|
+
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
561
562
|
short_path: shortPath,
|
|
562
563
|
user_agent: 'cookbook-doctest/birthday-greeting',
|
|
563
564
|
})
|
|
@@ -566,7 +567,7 @@ if (resolved.route_path !== '/forms/birthday-greeting') {
|
|
|
566
567
|
}
|
|
567
568
|
|
|
568
569
|
const now = new Date().toISOString()
|
|
569
|
-
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
|
|
570
|
+
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
570
571
|
short_path: shortPath,
|
|
571
572
|
opened_at: now,
|
|
572
573
|
form_submitted_at: now,
|
|
@@ -159,7 +159,7 @@ toolId = smsToolResp.data.id!
|
|
|
159
159
|
## 003 — create the LLM tool
|
|
160
160
|
|
|
161
161
|
The Triage agent calls a chat-completion endpoint to classify each
|
|
162
|
-
row. The tool's `intent: '
|
|
162
|
+
row. The tool's `intent: 'llm_enrichment'` distinguishes it from
|
|
163
163
|
the SMS tool above; `tool_body_type: 'rest_api'` plus an
|
|
164
164
|
OpenAI-compatible `base_url` lets the platform route Ollama,
|
|
165
165
|
Anthropic, OpenAI, or any other compatible inference server
|
|
@@ -174,7 +174,7 @@ discriminated-union schema even though Ollama ignores the header.
|
|
|
174
174
|
const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
175
175
|
name: `Cookbook LLM Tool ${runSuffix}`,
|
|
176
176
|
description: 'Ollama-backed chat-completion endpoint for Contact Us triage classification.',
|
|
177
|
-
intent: '
|
|
177
|
+
intent: 'llm_enrichment',
|
|
178
178
|
status: 'active',
|
|
179
179
|
datalake_id: ctx.datalakeId,
|
|
180
180
|
body: {
|
|
@@ -454,7 +454,7 @@ const submissionIds = [
|
|
|
454
454
|
]
|
|
455
455
|
const idList = submissionIds.map((id) => `'${id}'`).join(', ')
|
|
456
456
|
|
|
457
|
-
const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
|
|
457
|
+
const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
458
458
|
sql_where_clause: `submission_id IN (${idList})`,
|
|
459
459
|
mode: 'live',
|
|
460
460
|
manual_override: true,
|
|
@@ -465,7 +465,7 @@ ctx.runBatchId = runResp.data.batch_id!
|
|
|
465
465
|
const deadline = Date.now() + 240_000
|
|
466
466
|
let status: string | null = null
|
|
467
467
|
while (Date.now() < deadline) {
|
|
468
|
-
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
|
|
468
|
+
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
|
|
469
469
|
status = log.status ?? null
|
|
470
470
|
if (status && status !== 'pending') break
|
|
471
471
|
await new Promise((r) => setTimeout(r, 2_000))
|
|
@@ -489,7 +489,7 @@ would mean the agent's output did not actually steer the decision;
|
|
|
489
489
|
this step fails loudly in that case.
|
|
490
490
|
|
|
491
491
|
```typescript
|
|
492
|
-
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
|
|
492
|
+
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
|
|
493
493
|
const ourWels = (wfLogs.data ?? []).filter(
|
|
494
494
|
(w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
|
|
495
495
|
)
|
|
@@ -401,7 +401,7 @@ leaves `:pending`. A `:partial` status is expected and fine here:
|
|
|
401
401
|
one row passed and one was filtered.
|
|
402
402
|
|
|
403
403
|
```typescript
|
|
404
|
-
const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
|
|
404
|
+
const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
405
405
|
sql_where_clause: `ra.batch_id IN ('${ctx.batchVerified}', '${ctx.batchUnverified}')`,
|
|
406
406
|
mode: 'live',
|
|
407
407
|
manual_override: false,
|
|
@@ -412,7 +412,7 @@ ctx.runBatchId = runResp.data.batch_id!
|
|
|
412
412
|
const deadline = Date.now() + 120_000
|
|
413
413
|
let status: string | null = null
|
|
414
414
|
while (Date.now() < deadline) {
|
|
415
|
-
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
|
|
415
|
+
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
|
|
416
416
|
status = log.status ?? null
|
|
417
417
|
if (status && status !== 'pending') break
|
|
418
418
|
await new Promise((r) => setTimeout(r, 2_000))
|
|
@@ -433,7 +433,7 @@ passed — or both were filtered — would mean the filter is not
|
|
|
433
433
|
actually evaluating both `customer.phone` and `customer.tax_id`.
|
|
434
434
|
|
|
435
435
|
```typescript
|
|
436
|
-
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
|
|
436
|
+
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
|
|
437
437
|
const ourWels = (wfLogs.data ?? []).filter(
|
|
438
438
|
(w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
|
|
439
439
|
)
|
|
@@ -511,7 +511,7 @@ the connected-app frontend does when the customer opens the
|
|
|
511
511
|
payment portal, closing the SMS → reply loop end-to-end.
|
|
512
512
|
|
|
513
513
|
```typescript
|
|
514
|
-
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
|
|
514
|
+
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
515
515
|
short_path: ctx.dunningShortPath,
|
|
516
516
|
user_agent: 'cookbook-doctest/dunning-sms',
|
|
517
517
|
})
|
|
@@ -523,7 +523,7 @@ if (!(resolved.message?.body ?? '').includes('needs your attention')) {
|
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
const now = new Date().toISOString()
|
|
526
|
-
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
|
|
526
|
+
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
527
527
|
short_path: ctx.dunningShortPath,
|
|
528
528
|
opened_at: now,
|
|
529
529
|
form_submitted_at: now,
|
|
@@ -399,7 +399,7 @@ leaves `:pending`. A `:partial` status is expected and fine here:
|
|
|
399
399
|
one row passed and one was filtered.
|
|
400
400
|
|
|
401
401
|
```typescript
|
|
402
|
-
const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
|
|
402
|
+
const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
403
403
|
sql_where_clause: `rpa.batch_id IN ('${ctx.batchActive}', '${ctx.batchSuspended}')`,
|
|
404
404
|
mode: 'live',
|
|
405
405
|
manual_override: false,
|
|
@@ -410,7 +410,7 @@ ctx.runBatchId = runResp.data.batch_id!
|
|
|
410
410
|
const deadline = Date.now() + 120_000
|
|
411
411
|
let status: string | null = null
|
|
412
412
|
while (Date.now() < deadline) {
|
|
413
|
-
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
|
|
413
|
+
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
|
|
414
414
|
status = log.status ?? null
|
|
415
415
|
if (status && status !== 'pending') break
|
|
416
416
|
await new Promise((r) => setTimeout(r, 2_000))
|
|
@@ -431,7 +431,7 @@ filtered — would mean the filter is not actually evaluating
|
|
|
431
431
|
`payment_account.status`.
|
|
432
432
|
|
|
433
433
|
```typescript
|
|
434
|
-
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
|
|
434
|
+
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
|
|
435
435
|
const ourWels = (wfLogs.data ?? []).filter(
|
|
436
436
|
(w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
|
|
437
437
|
)
|
|
@@ -509,7 +509,7 @@ the connected-app frontend does when the account holder opens the
|
|
|
509
509
|
KYC portal, closing the SMS → reply loop end-to-end.
|
|
510
510
|
|
|
511
511
|
```typescript
|
|
512
|
-
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
|
|
512
|
+
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
513
513
|
short_path: ctx.kycShortPath,
|
|
514
514
|
user_agent: 'cookbook-doctest/kyc-notification',
|
|
515
515
|
})
|
|
@@ -521,7 +521,7 @@ if (!(resolved.message?.body ?? '').includes('Alvera PR notification')) {
|
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
const now = new Date().toISOString()
|
|
524
|
-
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
|
|
524
|
+
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
525
525
|
short_path: ctx.kycShortPath,
|
|
526
526
|
opened_at: now,
|
|
527
527
|
form_submitted_at: now,
|
|
@@ -106,7 +106,7 @@ toolId = smsToolResp.data.id!
|
|
|
106
106
|
|
|
107
107
|
The Sanctions Review agent calls a chat-completion endpoint to
|
|
108
108
|
disambiguate each gray-zone screening. The tool's
|
|
109
|
-
`intent: '
|
|
109
|
+
`intent: 'llm_enrichment'` distinguishes it from the SMS tool
|
|
110
110
|
above; `tool_body_type: 'rest_api'` plus an OpenAI-compatible
|
|
111
111
|
`base_url` lets the platform route Ollama, Anthropic, OpenAI, or
|
|
112
112
|
any other compatible inference server through the same wire
|
|
@@ -119,7 +119,7 @@ though Ollama ignores the header.
|
|
|
119
119
|
const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
120
120
|
name: `Cookbook LLM Tool ${runSuffix}`,
|
|
121
121
|
description: 'Ollama-backed chat-completion endpoint for sanctions-screening disambiguation.',
|
|
122
|
-
intent: '
|
|
122
|
+
intent: 'llm_enrichment',
|
|
123
123
|
status: 'active',
|
|
124
124
|
datalake_id: ctx.datalakeId,
|
|
125
125
|
body: {
|
|
@@ -487,7 +487,7 @@ is generous because the gray-zone row's enrichment is a live LLM
|
|
|
487
487
|
inference call.
|
|
488
488
|
|
|
489
489
|
```typescript
|
|
490
|
-
const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
|
|
490
|
+
const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
491
491
|
sql_where_clause: `rcs.batch_id IN ('${ctx.batchGrayZone}', '${ctx.batchAutoClear}')`,
|
|
492
492
|
mode: 'live',
|
|
493
493
|
manual_override: false,
|
|
@@ -498,7 +498,7 @@ ctx.runBatchId = runResp.data.batch_id!
|
|
|
498
498
|
const deadline = Date.now() + 240_000
|
|
499
499
|
let status: string | null = null
|
|
500
500
|
while (Date.now() < deadline) {
|
|
501
|
-
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
|
|
501
|
+
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
|
|
502
502
|
status = log.status ?? null
|
|
503
503
|
if (status && status !== 'pending') break
|
|
504
504
|
await new Promise((r) => setTimeout(r, 2_000))
|
|
@@ -525,7 +525,7 @@ The pass-branch WEL carries exactly **two** action execution logs
|
|
|
525
525
|
the decision rather than firing every action.
|
|
526
526
|
|
|
527
527
|
```typescript
|
|
528
|
-
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
|
|
528
|
+
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
|
|
529
529
|
const ourWels = (wfLogs.data ?? []).filter(
|
|
530
530
|
(w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
|
|
531
531
|
)
|
|
@@ -169,7 +169,7 @@ toolId = smsToolResp.data.id!
|
|
|
169
169
|
## 003 — create the LLM tool
|
|
170
170
|
|
|
171
171
|
The Score Lead agent calls a chat-completion endpoint to classify each
|
|
172
|
-
row. The tool's `intent: '
|
|
172
|
+
row. The tool's `intent: 'llm_enrichment'` distinguishes it from the
|
|
173
173
|
SMS tool above; `tool_body_type: 'rest_api'` plus an OpenAI-compatible
|
|
174
174
|
`base_url` lets the platform route Ollama, Anthropic, OpenAI, or any
|
|
175
175
|
other compatible inference server through the same wire format. Local
|
|
@@ -182,7 +182,7 @@ Ollama itself ignores the header.
|
|
|
182
182
|
const llmToolResp = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
183
183
|
name: `Cookbook LLM Tool ${runSuffix}`,
|
|
184
184
|
description: 'Ollama-backed chat-completion endpoint for Score Lead classification.',
|
|
185
|
-
intent: '
|
|
185
|
+
intent: 'llm_enrichment',
|
|
186
186
|
status: 'active',
|
|
187
187
|
datalake_id: ctx.datalakeId,
|
|
188
188
|
body: {
|
|
@@ -459,7 +459,7 @@ live LLM inference call.
|
|
|
459
459
|
const regulatedTable = `regulated_${ctx.gtName}`
|
|
460
460
|
const batchList = (ctx.leadBatchIds as string[]).map((b) => `'${b}'`).join(', ')
|
|
461
461
|
|
|
462
|
-
const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
|
|
462
|
+
const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
463
463
|
sql_where_clause: `${regulatedTable}.batch_id IN (${batchList})`,
|
|
464
464
|
mode: 'live',
|
|
465
465
|
manual_override: true,
|
|
@@ -470,7 +470,7 @@ ctx.runBatchId = runResp.data.batch_id!
|
|
|
470
470
|
const deadline = Date.now() + 240_000
|
|
471
471
|
let status: string | null = null
|
|
472
472
|
while (Date.now() < deadline) {
|
|
473
|
-
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
|
|
473
|
+
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
|
|
474
474
|
status = log.status ?? null
|
|
475
475
|
if (status && status !== 'pending') break
|
|
476
476
|
await new Promise((r) => setTimeout(r, 2_000))
|
|
@@ -495,7 +495,7 @@ would mean the agent's output did not actually steer the decision;
|
|
|
495
495
|
this step fails loudly in that case.
|
|
496
496
|
|
|
497
497
|
```typescript
|
|
498
|
-
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
|
|
498
|
+
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
|
|
499
499
|
const ourWels = (wfLogs.data ?? []).filter(
|
|
500
500
|
(w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
|
|
501
501
|
)
|
|
@@ -388,7 +388,7 @@ leaves `:pending`. A `:partial` status is expected and fine here:
|
|
|
388
388
|
one row passed and one was filtered.
|
|
389
389
|
|
|
390
390
|
```typescript
|
|
391
|
-
const runResp = await api.workflows.run(tenantSlug, ctx.workflowSlug, {
|
|
391
|
+
const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
392
392
|
sql_where_clause: `ra.batch_id IN ('${ctx.batchWithPhone}', '${ctx.batchNoPhone}')`,
|
|
393
393
|
mode: 'live',
|
|
394
394
|
manual_override: false,
|
|
@@ -399,7 +399,7 @@ ctx.runBatchId = runResp.data.batch_id!
|
|
|
399
399
|
const deadline = Date.now() + 120_000
|
|
400
400
|
let status: string | null = null
|
|
401
401
|
while (Date.now() < deadline) {
|
|
402
|
-
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, ctx.workflowSlug, ctx.runLogId)
|
|
402
|
+
const { data: log } = await api.workflows.batchLogs.refresh(tenantSlug, datalakeSlug, ctx.workflowSlug, ctx.runLogId)
|
|
403
403
|
status = log.status ?? null
|
|
404
404
|
if (status && status !== 'pending') break
|
|
405
405
|
await new Promise((r) => setTimeout(r, 2_000))
|
|
@@ -420,7 +420,7 @@ filtered — would mean the filter is not actually evaluating
|
|
|
420
420
|
`customer.phone`.
|
|
421
421
|
|
|
422
422
|
```typescript
|
|
423
|
-
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, ctx.workflowSlug)
|
|
423
|
+
const { data: wfLogs } = await api.workflows.workflowLogs.list(tenantSlug, datalakeSlug, ctx.workflowSlug)
|
|
424
424
|
const ourWels = (wfLogs.data ?? []).filter(
|
|
425
425
|
(w) => (w as { batch_id?: string }).batch_id === ctx.runBatchId,
|
|
426
426
|
)
|
|
@@ -498,7 +498,7 @@ the connected-app frontend does when the customer opens the
|
|
|
498
498
|
billing portal, closing the SMS → reply loop end-to-end.
|
|
499
499
|
|
|
500
500
|
```typescript
|
|
501
|
-
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, ctx.connectedAppSlug, {
|
|
501
|
+
const { data: resolved } = await api.connectedApps.resolvePage(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
502
502
|
short_path: ctx.welcomeShortPath,
|
|
503
503
|
user_agent: 'cookbook-doctest/welcome-sms',
|
|
504
504
|
})
|
|
@@ -510,7 +510,7 @@ if (!(resolved.message?.body ?? '').includes('welcome to Alvera Billing')) {
|
|
|
510
510
|
}
|
|
511
511
|
|
|
512
512
|
const now = new Date().toISOString()
|
|
513
|
-
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, ctx.connectedAppSlug, {
|
|
513
|
+
const { data: tracked } = await api.connectedApps.updateMessageTracking(tenantSlug, datalakeSlug, ctx.connectedAppSlug, {
|
|
514
514
|
short_path: ctx.welcomeShortPath,
|
|
515
515
|
opened_at: now,
|
|
516
516
|
form_submitted_at: now,
|
package/.agent/tools.md
CHANGED
|
@@ -103,13 +103,12 @@ create time:
|
|
|
103
103
|
voice → rest_api (Twilio Voice, etc.)
|
|
104
104
|
export → s3, aws_lambda
|
|
105
105
|
status_poller → cloud_watch_log_group, rest_api
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
enrichment vs free-form chat)
|
|
106
|
+
llm_enrichment → rest_api (against an OpenAI-compatible LLM
|
|
107
|
+
endpoint; the platform appends
|
|
108
|
+
/chat/completions to base_url; the
|
|
109
|
+
tool is the complete LLM provider
|
|
110
|
+
adapter and AI agents bind it for
|
|
111
|
+
structured row enrichment)
|
|
113
112
|
```
|
|
114
113
|
|
|
115
114
|
A misaligned pair (e.g. `intent: sms` + `tool_body_type: sftp`)
|