@alexkroman1/aai-cli 6.10.1 → 7.0.0
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/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
- package/dist/{_bundler-C2BDwU7r.mjs → _bundler-B4RqNF5Z.mjs} +2 -2
- package/dist/_dev-env.d.ts +22 -2
- package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-CiKFH9qw.mjs} +38 -47
- package/dist/_dev-server.d.ts +0 -17
- package/dist/{_init-C57bWAQ8.mjs → _init-CE4HKdgV.mjs} +3 -2
- package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
- package/dist/_preflight.d.ts +1 -1
- package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-nRFaBEPJ.mjs} +2 -2
- package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
- package/dist/{_templates-Dxb_P2Wz.mjs → _templates-FI3xPEMj.mjs} +1 -1
- package/dist/_workflow-scan.d.ts +108 -0
- package/dist/{build-Dfza2pRI.mjs → build-6jfaakgc.mjs} +5 -2
- package/dist/cli.mjs +30 -25
- package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
- package/dist/{deploy-B8lubiRT.mjs → deploy-DvYeD8ia.mjs} +5 -5
- package/dist/{dev-Uq5ujP8a.mjs → dev-B59JIfWO.mjs} +1 -1
- package/dist/{eject-31gjtaHF.mjs → eject-C9WJyyr2.mjs} +1 -1
- package/dist/{init-l6xfU4xX.mjs → init-CpJVjEg0.mjs} +3 -3
- package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
- package/dist/{logs-BWVGSjLR.mjs → logs-9q-psa4q.mjs} +2 -2
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +93 -14
- package/dist/scaffold/package.json +5 -3
- package/dist/scaffold/server.mjs +13 -4
- package/dist/scaffold/vite.config.ts +1 -1
- package/dist/{secret-BuMuFR4B.mjs → secret-CVvSLIDV.mjs} +2 -2
- package/dist/{storage-DzRZ-eCw.mjs → storage-BvUrnvM3.mjs} +2 -2
- package/dist/{studio-BCNUpDgP.mjs → studio--MUV0cid.mjs} +4 -4
- package/dist/templates/call-audit/agent.test.ts +818 -0
- package/dist/templates/call-audit/agent.ts +171 -0
- package/dist/templates/call-audit/client.tsx +198 -0
- package/dist/templates/call-audit/workflows/audit.ts +298 -0
- package/dist/templates/call-audit/workflows/ingest.ts +251 -0
- package/dist/templates/call-audit/workflows/media.ts +637 -0
- package/dist/templates/call-audit/workflows/summarize.ts +201 -0
- package/dist/templates/call-audit/workflows/sync-api.ts +44 -0
- package/dist/templates/dispatch-center/agent.test.ts +149 -25
- package/dist/templates/dispatch-center/client.tsx +239 -129
- package/dist/templates/dispatch-center/shared.ts +99 -1
- package/dist/templates/dispatch-center/system-prompt.md +3 -1
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
- package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
- package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
- package/dist/templates/embedded-assets/agent.test.ts +17 -14
- package/dist/templates/health-assistant/agent.test.ts +22 -4
- package/dist/templates/infocom-adventure/agent.test.ts +4 -6
- package/dist/templates/infocom-adventure/client.tsx +246 -164
- package/dist/templates/link-digest/agent.test.ts +24 -19
- package/dist/templates/link-digest/client.tsx +47 -61
- package/dist/templates/link-digest/workflows/digest.ts +19 -31
- package/dist/templates/night-owl/agent.test.ts +70 -19
- package/dist/templates/night-owl/agent.ts +5 -0
- package/dist/templates/night-owl/client.tsx +56 -56
- package/dist/templates/night-owl/shared.ts +24 -0
- package/dist/templates/night-owl/tools/recommend.ts +18 -6
- package/dist/templates/pizza-ordering/agent.test.ts +27 -27
- package/dist/templates/pizza-ordering/client.tsx +9 -26
- package/dist/templates/plan-and-execute/agent.test.ts +99 -58
- package/dist/templates/plan-and-execute/agent.ts +1 -1
- package/dist/templates/plan-and-execute/client.tsx +12 -15
- package/dist/templates/plan-and-execute/shared.ts +71 -2
- package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
- package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
- package/dist/templates/podcast-digest/agent.test.ts +746 -0
- package/dist/templates/podcast-digest/agent.ts +139 -0
- package/dist/templates/podcast-digest/client.tsx +154 -0
- package/dist/templates/podcast-digest/workflows/digest.ts +411 -0
- package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
- package/dist/templates/podcast-digest/workflows/slack.ts +209 -0
- package/dist/templates/recap-workflow/agent.test.ts +107 -96
- package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
- package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
- package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
- package/dist/templates/recap-workflow/workflows/recap.ts +48 -41
- package/dist/templates/redline/agent.test.ts +17 -15
- package/dist/templates/redline/client.tsx +12 -12
- package/dist/templates/redline/workflows/redline.ts +19 -31
- package/dist/templates/research-workflow/agent.test.ts +60 -59
- package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
- package/dist/templates/research-workflow/tools/research_status.ts +9 -11
- package/dist/templates/research-workflow/workflows/research.ts +44 -61
- package/dist/templates/retail/agent.test.ts +26 -23
- package/dist/templates/retail/client.tsx +226 -117
- package/dist/templates/retail/registry.test.ts +38 -6
- package/dist/templates/retail/store.test.ts +82 -15
- package/dist/templates/retail/store.ts +174 -47
- package/dist/templates/retail/system-prompt.md +11 -2
- package/dist/templates/retail/tools/cancel_pending_order.ts +2 -2
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -2
- package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
- package/dist/templates/retail/tools/get_item_details.ts +3 -4
- package/dist/templates/retail/tools/get_order_details.ts +3 -4
- package/dist/templates/retail/tools/get_product_details.ts +3 -4
- package/dist/templates/retail/tools/get_user_details.ts +2 -2
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
- package/dist/templates/retail/tools/modify_pending_order_address.ts +2 -2
- package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -3
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -2
- package/dist/templates/retail/tools/modify_user_address.ts +2 -2
- package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -2
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +8 -4
- package/dist/templates/solo-rpg/agent.test.ts +227 -48
- package/dist/templates/solo-rpg/client.tsx +38 -37
- package/dist/templates/solo-rpg/shared.ts +145 -19
- package/dist/templates/solo-rpg/system-prompt.md +3 -2
- package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
- package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
- package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
- package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
- package/dist/templates/spoken-summary/agent.test.ts +314 -0
- package/dist/templates/spoken-summary/agent.ts +155 -0
- package/dist/templates/spoken-summary/client.tsx +185 -0
- package/dist/templates/spoken-summary/workflows/summarize.ts +237 -0
- package/dist/templates/spoken-summary/workflows/transcribe.ts +138 -0
- package/dist/templates/support-line/agent.test.ts +11 -16
- package/dist/templates/support-line/agent.ts +1 -1
- package/dist/templates/support-line/client.tsx +9 -9
- package/dist/templates/support-line/nodes.ts +100 -0
- package/dist/templates/support-line/procedure.ts +407 -0
- package/dist/templates/support-line/tools/answer_question.ts +17 -4
- package/dist/templates/transcription-workflow/agent.test.ts +246 -123
- package/dist/templates/transcription-workflow/agent.ts +21 -7
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +79 -180
- package/dist/templates/transcription-workflow/workflows/normalize.ts +247 -0
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +10 -9
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +26 -94
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +55 -40
- package/dist/templates/transcription-workflow/workflows/wav.ts +31 -31
- package/dist/templates/travel-concierge/agent.test.ts +64 -33
- package/dist/templates/travel-concierge/client.tsx +11 -23
- package/dist/templates/travel-concierge/routing.ts +34 -15
- package/dist/templates/travel-concierge/shared.ts +70 -3
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_excursion.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_hotel.ts +2 -2
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +2 -2
- package/dist/templates/travel-concierge/tools/update_ticket.ts +2 -2
- package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-COxnqstQ.mjs} +221 -3
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-CFpxOFfQ.mjs → workflow-D2AQf2Pl.mjs} +28 -1
- package/dist/workflow-bundler.d.ts +6 -1
- package/dist/workflow.d.ts +1 -1
- package/package.json +5 -4
- package/dist/templates/support-line/graph.ts +0 -224
- package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
- /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Copyright 2026 the AAI authors. MIT license.
|
|
2
|
+
/**
|
|
3
|
+
* The first leg: the uploaded recording becomes text.
|
|
4
|
+
*
|
|
5
|
+
* Three steps and a durable wait, over AssemblyAI's ASYNC transcription API:
|
|
6
|
+
*
|
|
7
|
+
* ```text
|
|
8
|
+
* uploadToProvider one step → the file, streamed, and the URL it answered
|
|
9
|
+
* createJob one step → the transcript id
|
|
10
|
+
* pollTranscript one step + a durable sleep, until the text comes back
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* **Every one of them is four lines, because the SDK owns the endpoint.**
|
|
14
|
+
* `stepTranscribeUpload` / `stepTranscribeSubmit` / `stepTranscribePoll` on
|
|
15
|
+
* `@alexkroman1/aai/step` — reached here through their `*Classified` callers on
|
|
16
|
+
* `@alexkroman1/aai/step-errors` — carry the URL, the raw-key auth, the windowed
|
|
17
|
+
* streaming upload, the PLURAL `speech_models` field and the failure
|
|
18
|
+
* classification — all of which this file used to spell out, and all of which
|
|
19
|
+
* `transcription-workflow` used to spell out again, differently worded and
|
|
20
|
+
* identical in behaviour. What is left here is what is genuinely this app's:
|
|
21
|
+
* which steps to cut the job into, how long to wait, and what to report.
|
|
22
|
+
*
|
|
23
|
+
* ## The steps are still OURS, and they have to be
|
|
24
|
+
*
|
|
25
|
+
* The SDK cannot ship a `"use step"`: the Workflow DevKit's builder transforms
|
|
26
|
+
* exactly the files under this `workflows/` directory, so a directive inside a
|
|
27
|
+
* dependency would be transformed by nothing and would run inline with no
|
|
28
|
+
* journal and no retry, silently. The SDK owns what happens INSIDE a step; the
|
|
29
|
+
* boundaries — which is to say, what gets journaled and what a retry repeats —
|
|
30
|
+
* are the app's.
|
|
31
|
+
*
|
|
32
|
+
* **The async API rather than the sync one, and the choice is about the FORM.**
|
|
33
|
+
* The sync endpoint (`stepTranscribeSync`) answers inside the request and pays
|
|
34
|
+
* for it with a hard 120-second, 40 MB cap, so a longer recording has to be cut
|
|
35
|
+
* into segments and fanned out — which is a whole subject, and it has a
|
|
36
|
+
* template (`transcription-workflow`, which shows that cut three ways and
|
|
37
|
+
* measures them). This app's subject is the ROUND TRIP, so the transcription is
|
|
38
|
+
* the one leg that should be as boring as possible.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { report, uploadInfo } from "@alexkroman1/aai/step";
|
|
42
|
+
import {
|
|
43
|
+
stepTranscribePollClassified,
|
|
44
|
+
stepTranscribeSubmitClassified,
|
|
45
|
+
stepTranscribeUploadClassified,
|
|
46
|
+
} from "@alexkroman1/aai/step-errors";
|
|
47
|
+
import { countWords, formatBytes } from "@alexkroman1/aai/utils";
|
|
48
|
+
|
|
49
|
+
/** How long between polls of a submitted job. */
|
|
50
|
+
export const POLL_INTERVAL = "10s";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Polls before the run gives up on a job.
|
|
54
|
+
*
|
|
55
|
+
* At {@link POLL_INTERVAL} this is an hour, well past what the async API takes
|
|
56
|
+
* for any recording it accepts. Bounded rather than endless because a job that
|
|
57
|
+
* never leaves `queued` is a run that would otherwise be replayed forever.
|
|
58
|
+
*/
|
|
59
|
+
export const MAX_POLLS = 360;
|
|
60
|
+
|
|
61
|
+
/** What the first leg hands the second. */
|
|
62
|
+
export type Transcript = {
|
|
63
|
+
/** The FILENAME the uploader gave, not the opaque id — this reaches the page. */
|
|
64
|
+
source: string;
|
|
65
|
+
/** The provider's own measurement of the recording, in milliseconds. */
|
|
66
|
+
durationMs: number;
|
|
67
|
+
/** What was said. */
|
|
68
|
+
text: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Upload the recording to the provider and answer with the URL it gave.
|
|
73
|
+
*
|
|
74
|
+
* Its OWN step, and that is a measurement rather than a preference: folded into
|
|
75
|
+
* the submit, a fault in the JSON body — a deprecated field, a bad model name —
|
|
76
|
+
* makes the DevKit retry the whole step and re-upload the recording on every
|
|
77
|
+
* attempt. A retry that repeats the expensive half to fix the cheap half is not
|
|
78
|
+
* a retry. The `upload_url` is short-lived, so the risk being taken is that it
|
|
79
|
+
* expires before the next step runs; that costs one fresh upload, once, instead
|
|
80
|
+
* of five.
|
|
81
|
+
*
|
|
82
|
+
* The `Classified` callers on `@alexkroman1/aai/step-errors` are the SDK's own
|
|
83
|
+
* `stepTranscribe*` plus `throwStepError` and nothing else, which is what turns
|
|
84
|
+
* the SDK's `TranscribeError` into the DevKit's verdict — a missing key and a
|
|
85
|
+
* 400 stop, a 429 waits as long as the service asked. Every step here ends the
|
|
86
|
+
* same way for the same reason.
|
|
87
|
+
*/
|
|
88
|
+
export async function uploadToProvider(uploadId: string): Promise<{ audioUrl: string }> {
|
|
89
|
+
"use step";
|
|
90
|
+
|
|
91
|
+
const stored = await uploadInfo(uploadId);
|
|
92
|
+
await report(
|
|
93
|
+
`Uploading ${stored.name || uploadId} (${formatBytes(stored.size)}) for transcription.`,
|
|
94
|
+
);
|
|
95
|
+
return await stepTranscribeUploadClassified(uploadId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Retries beyond the default 3: an upload is the one call here worth another attempt. */
|
|
99
|
+
uploadToProvider.maxRetries = 5;
|
|
100
|
+
|
|
101
|
+
/** Create the transcription job, and answer with the id that outlives this run. */
|
|
102
|
+
export async function createJob(audioUrl: string): Promise<{ id: string }> {
|
|
103
|
+
"use step";
|
|
104
|
+
|
|
105
|
+
const job = await stepTranscribeSubmitClassified(audioUrl);
|
|
106
|
+
await report(`Transcribing — job ${job.id}.`);
|
|
107
|
+
return job;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Ask once whether the job has finished, and read it when it has.
|
|
112
|
+
*
|
|
113
|
+
* One request answers both, which is the SDK's doing and worth knowing: this
|
|
114
|
+
* used to poll for a status and then fetch the identical URL a second time for
|
|
115
|
+
* the text. The body still branches on `done` rather than on a status string —
|
|
116
|
+
* a provider's vocabulary must not be interpreted in a body, where a new status
|
|
117
|
+
* would read as "not finished yet" forever.
|
|
118
|
+
*/
|
|
119
|
+
export async function pollTranscript(
|
|
120
|
+
uploadId: string,
|
|
121
|
+
id: string,
|
|
122
|
+
): Promise<{ done: false } | { done: true; transcript: Transcript }> {
|
|
123
|
+
"use step";
|
|
124
|
+
|
|
125
|
+
const progress = await stepTranscribePollClassified(id);
|
|
126
|
+
if (!progress.done) return { done: false };
|
|
127
|
+
|
|
128
|
+
const stored = await uploadInfo(uploadId);
|
|
129
|
+
await report(`Transcribed ${countWords(progress.transcript.text)} words.`);
|
|
130
|
+
return {
|
|
131
|
+
done: true,
|
|
132
|
+
transcript: {
|
|
133
|
+
source: stored.name || uploadId,
|
|
134
|
+
durationMs: progress.transcript.durationMs,
|
|
135
|
+
text: progress.transcript.text,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/// <reference types="vite/client" />
|
|
2
2
|
|
|
3
|
-
import type { GenerateFn, ToolContext } from "@alexkroman1/aai";
|
|
4
3
|
import {
|
|
5
4
|
createToolContext,
|
|
6
|
-
runTool,
|
|
7
5
|
stubGenerate,
|
|
6
|
+
toolRunner,
|
|
8
7
|
withDiscoveredTools,
|
|
9
8
|
} from "@alexkroman1/aai/testing";
|
|
10
9
|
import { describe, expect, test } from "vitest";
|
|
@@ -23,7 +22,7 @@ const agentDef = withDiscoveredTools(
|
|
|
23
22
|
import.meta.glob("./tools/*.ts", { eager: true }),
|
|
24
23
|
);
|
|
25
24
|
|
|
26
|
-
import { MAX_ATTEMPTS, runCorrectiveRag } from "./
|
|
25
|
+
import { MAX_ATTEMPTS, runCorrectiveRag } from "./procedure.ts";
|
|
27
26
|
import {
|
|
28
27
|
ANSWER_SYSTEM,
|
|
29
28
|
ANSWERS_SYSTEM,
|
|
@@ -95,15 +94,11 @@ function scriptedModel(script: Script = {}) {
|
|
|
95
94
|
return { generate, calls };
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
* and its "no such tool" message are `runTool`'s (`@alexkroman1/aai/testing`);
|
|
104
|
-
* what is local is only which agent they run against. */
|
|
105
|
-
const run = (name: string, args: Record<string, unknown>, ctx: ToolContext) =>
|
|
106
|
-
runTool(agentDef, name, args, ctx);
|
|
97
|
+
/** A tool by the name the model calls it by, bound to this agent. The lookup,
|
|
98
|
+
* its "no such tool" message and the args-or-context shape are all
|
|
99
|
+
* `toolRunner`'s (`@alexkroman1/aai/testing`); what is local is only which
|
|
100
|
+
* agent it runs against. */
|
|
101
|
+
const run = toolRunner(agentDef);
|
|
107
102
|
|
|
108
103
|
/** Node names without the per-call suffix, for sequence assertions. */
|
|
109
104
|
function nodes(calls: string[]): string[] {
|
|
@@ -254,7 +249,7 @@ describe("answer_question", () => {
|
|
|
254
249
|
relevant: (id) => id === "D8",
|
|
255
250
|
answers: ["Area outages are on the status page, and rebooting will not help."],
|
|
256
251
|
});
|
|
257
|
-
const ctx =
|
|
252
|
+
const ctx = createToolContext({ generate });
|
|
258
253
|
const result = (await run("answer_question", { question: "is there an outage" }, ctx)) as {
|
|
259
254
|
answer: string;
|
|
260
255
|
sources: string[];
|
|
@@ -276,7 +271,7 @@ describe("answer_question", () => {
|
|
|
276
271
|
|
|
277
272
|
test("with nothing grounded it returns no answer and points at the ticket", async () => {
|
|
278
273
|
const { generate } = scriptedModel({ relevant: () => false });
|
|
279
|
-
const ctx =
|
|
274
|
+
const ctx = createToolContext({ generate });
|
|
280
275
|
const result = (await run("answer_question", { question: "do you sell phones" }, ctx)) as {
|
|
281
276
|
answer: null;
|
|
282
277
|
guidance: string;
|
|
@@ -302,8 +297,8 @@ describe("answer_question", () => {
|
|
|
302
297
|
// session ids would prove nothing extra, and `sessionSlot` could stop
|
|
303
298
|
// keying by session with this still passing.
|
|
304
299
|
const { generate } = scriptedModel({ relevant: (id) => id === "D2", answers: ["Reboot it."] });
|
|
305
|
-
const first =
|
|
306
|
-
const second =
|
|
300
|
+
const first = createToolContext({ generate });
|
|
301
|
+
const second = createToolContext({ generate });
|
|
307
302
|
|
|
308
303
|
await run("answer_question", { question: "how do I reboot" }, first);
|
|
309
304
|
expect(supportSlot.get(second).trace).toBeNull();
|
|
@@ -3,7 +3,7 @@ import { PRODUCT, supportProjection } from "./shared.ts";
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A support line that grades its own retrieval before it speaks — the
|
|
6
|
-
* self-RAG / CRAG graph, ported to voice. `
|
|
6
|
+
* self-RAG / CRAG graph, ported to voice. `procedure.ts` holds the loop, `prompts.ts`
|
|
7
7
|
* the attribution and the graders, `shared.ts` the knowledge base and why its
|
|
8
8
|
* retriever is lexical.
|
|
9
9
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "@alexkroman1/aai-ui/styles.css";
|
|
2
|
-
import { AutoScroll, client, useAgentState
|
|
2
|
+
import { AutoScroll, client, useAgentState } from "@alexkroman1/aai-ui";
|
|
3
3
|
import { PRODUCT, supportProjection } from "./shared.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -11,12 +11,11 @@ import { PRODUCT, supportProjection } from "./shared.ts";
|
|
|
11
11
|
* support line nobody can tell apart from one that just guesses well.
|
|
12
12
|
*/
|
|
13
13
|
function TraceSidebar() {
|
|
14
|
-
const theme = useTheme();
|
|
15
14
|
const support = useAgentState(supportProjection);
|
|
16
15
|
const trace = support.trace;
|
|
17
16
|
|
|
18
17
|
return (
|
|
19
|
-
<div className="flex h-full min-h-0 flex-col gap-4 p-4
|
|
18
|
+
<div className="flex h-full min-h-0 flex-col gap-4 p-4 text-aai-text">
|
|
20
19
|
<div>
|
|
21
20
|
<h3 className="text-sm font-bold uppercase tracking-wide opacity-60">{support.product}</h3>
|
|
22
21
|
<p className="text-xs opacity-50">
|
|
@@ -36,7 +35,7 @@ function TraceSidebar() {
|
|
|
36
35
|
scrollClassName="min-h-0 overflow-y-auto"
|
|
37
36
|
contentClassName="flex flex-col gap-4 pr-1"
|
|
38
37
|
>
|
|
39
|
-
<div className="rounded-lg p-3
|
|
38
|
+
<div className="rounded-lg p-3 bg-aai-surface">
|
|
40
39
|
<p className="text-[11px] font-bold uppercase tracking-wide opacity-60">Question</p>
|
|
41
40
|
<p className="text-sm">{trace.question}</p>
|
|
42
41
|
{trace.rewrites > 0 && (
|
|
@@ -54,10 +53,11 @@ function TraceSidebar() {
|
|
|
54
53
|
{trace.docs.map((doc) => (
|
|
55
54
|
<div
|
|
56
55
|
key={doc.id}
|
|
57
|
-
className=
|
|
58
|
-
|
|
56
|
+
className={`flex items-start gap-2 rounded-lg p-2 bg-aai-surface ${
|
|
57
|
+
doc.relevant ? "" : "opacity-50"
|
|
58
|
+
}`}
|
|
59
59
|
>
|
|
60
|
-
<span
|
|
60
|
+
<span className={doc.relevant ? "text-aai-primary" : "text-aai-text"}>
|
|
61
61
|
{doc.relevant ? "✓" : "×"}
|
|
62
62
|
</span>
|
|
63
63
|
<div className="min-w-0">
|
|
@@ -69,7 +69,7 @@ function TraceSidebar() {
|
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
71
|
<div className="flex flex-col gap-1">
|
|
72
|
-
<p className="text-[11px] font-bold uppercase tracking-wide opacity-60">
|
|
72
|
+
<p className="text-[11px] font-bold uppercase tracking-wide opacity-60">Procedure</p>
|
|
73
73
|
{trace.steps.map((entry, index) => (
|
|
74
74
|
// Steps are append-only within a run, so the index is stable.
|
|
75
75
|
<p key={`${index}-${entry.node}`} className="text-xs">
|
|
@@ -79,7 +79,7 @@ function TraceSidebar() {
|
|
|
79
79
|
))}
|
|
80
80
|
</div>
|
|
81
81
|
|
|
82
|
-
<div className="rounded-lg p-3
|
|
82
|
+
<div className="rounded-lg p-3 bg-aai-surface">
|
|
83
83
|
<p className="text-[11px] font-bold uppercase tracking-wide opacity-60">Verdict</p>
|
|
84
84
|
{trace.answer ? (
|
|
85
85
|
<p className="text-sm">{trace.answer}</p>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CRAG nodes' BODIES — one model call each, and nothing about routing.
|
|
3
|
+
*
|
|
4
|
+
* Split from `procedure.ts` when that file became an actual machine: the machine
|
|
5
|
+
* there declares which node runs next, and these are what each node does. The
|
|
6
|
+
* seam is worth having beyond the line count — everything here is a plain async
|
|
7
|
+
* function of its arguments, so a spec can drive one directly, and the machine
|
|
8
|
+
* holds no prompt text at all.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { GenerateFn } from "@alexkroman1/aai";
|
|
12
|
+
import {
|
|
13
|
+
ANSWER_SYSTEM,
|
|
14
|
+
ANSWERS_SYSTEM,
|
|
15
|
+
binaryScore,
|
|
16
|
+
DOC_GRADER_SYSTEM,
|
|
17
|
+
GROUNDED_SYSTEM,
|
|
18
|
+
REWRITE_SYSTEM,
|
|
19
|
+
} from "./prompts.ts";
|
|
20
|
+
import type { Doc, GradedDoc } from "./shared.ts";
|
|
21
|
+
import { formatDoc } from "./shared.ts";
|
|
22
|
+
|
|
23
|
+
/** A closed verdict from one of the graders. */
|
|
24
|
+
export interface Verdict {
|
|
25
|
+
pass: boolean;
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `grade_documents` — one closed question per document, all at once. */
|
|
30
|
+
export async function gradeDocuments(
|
|
31
|
+
generate: GenerateFn,
|
|
32
|
+
question: string,
|
|
33
|
+
docs: readonly Doc[],
|
|
34
|
+
): Promise<GradedDoc[]> {
|
|
35
|
+
return await Promise.all(
|
|
36
|
+
docs.map(async (doc) => {
|
|
37
|
+
const { object } = await generate({
|
|
38
|
+
system: DOC_GRADER_SYSTEM,
|
|
39
|
+
prompt: `Caller's question: ${question}\n\nDocument:\n${formatDoc(doc)}`,
|
|
40
|
+
schema: binaryScore,
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
id: doc.id,
|
|
44
|
+
title: doc.title,
|
|
45
|
+
relevant: object.score === "yes",
|
|
46
|
+
reason: object.reason,
|
|
47
|
+
};
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** `transform_query` — their question re-writer, aimed at a keyword index. */
|
|
53
|
+
export async function transformQuery(generate: GenerateFn, question: string): Promise<string> {
|
|
54
|
+
const { text } = await generate({
|
|
55
|
+
system: REWRITE_SYSTEM,
|
|
56
|
+
prompt: `Caller's question: ${question}`,
|
|
57
|
+
});
|
|
58
|
+
const rewritten = text.trim().replace(/^["']|["']$/g, "");
|
|
59
|
+
// A rewriter that returns nothing usable must not empty the query — that
|
|
60
|
+
// would retrieve zero documents and read as "we have nothing on this".
|
|
61
|
+
return rewritten.length > 0 ? rewritten : question;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `generate` — the answer, from the graded-relevant documents only. */
|
|
65
|
+
export async function generateAnswer(
|
|
66
|
+
generate: GenerateFn,
|
|
67
|
+
question: string,
|
|
68
|
+
docs: readonly Doc[],
|
|
69
|
+
): Promise<string> {
|
|
70
|
+
const { text } = await generate({
|
|
71
|
+
system: ANSWER_SYSTEM,
|
|
72
|
+
prompt: `Documents:\n\n${docs.map(formatDoc).join("\n\n")}\n\nCaller's question: ${question}`,
|
|
73
|
+
});
|
|
74
|
+
return text.trim();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The two generation graders — same closed question, different subject. */
|
|
78
|
+
async function grade(generate: GenerateFn, system: string, prompt: string): Promise<Verdict> {
|
|
79
|
+
const { object } = await generate({ system, prompt, schema: binaryScore });
|
|
80
|
+
return { pass: object.score === "yes", reason: object.reason };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** `grade_generation_v_documents` — is the answer IN the documents? */
|
|
84
|
+
export async function gradeGrounded(
|
|
85
|
+
generate: GenerateFn,
|
|
86
|
+
docs: readonly Doc[],
|
|
87
|
+
answer: string,
|
|
88
|
+
): Promise<Verdict> {
|
|
89
|
+
const facts = docs.map(formatDoc).join("\n\n");
|
|
90
|
+
return await grade(generate, GROUNDED_SYSTEM, `Facts:\n\n${facts}\n\nAnswer: ${answer}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** `grade_generation_v_question` — grounded is not the same as useful. */
|
|
94
|
+
export async function gradeUseful(
|
|
95
|
+
generate: GenerateFn,
|
|
96
|
+
question: string,
|
|
97
|
+
answer: string,
|
|
98
|
+
): Promise<Verdict> {
|
|
99
|
+
return await grade(generate, ANSWERS_SYSTEM, `Question: ${question}\n\nAnswer: ${answer}`);
|
|
100
|
+
}
|