ai-capability-framework 1.0.3 → 1.1.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/CHANGELOG.md +42 -0
- package/README.md +38 -30
- package/ROADMAP.md +14 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +32 -1
- package/dist/discovery/bindings.d.ts +7 -0
- package/dist/discovery/bindings.d.ts.map +1 -0
- package/dist/discovery/bindings.js +24 -0
- package/dist/discovery/budget.d.ts +17 -0
- package/dist/discovery/budget.d.ts.map +1 -0
- package/dist/discovery/budget.js +77 -0
- package/dist/discovery/configuration.d.ts +10 -0
- package/dist/discovery/configuration.d.ts.map +1 -0
- package/dist/discovery/configuration.js +63 -0
- package/dist/discovery/corpus.d.ts +8 -0
- package/dist/discovery/corpus.d.ts.map +1 -0
- package/dist/discovery/corpus.js +64 -0
- package/dist/discovery/diagnostics.d.ts +15 -0
- package/dist/discovery/diagnostics.d.ts.map +1 -0
- package/dist/discovery/diagnostics.js +11 -0
- package/dist/discovery/discoverer.d.ts +23 -0
- package/dist/discovery/discoverer.d.ts.map +1 -0
- package/dist/discovery/discoverer.js +261 -0
- package/dist/discovery/embedding-configuration.d.ts +12 -0
- package/dist/discovery/embedding-configuration.d.ts.map +1 -0
- package/dist/discovery/embedding-configuration.js +81 -0
- package/dist/discovery/embedding-types.d.ts +75 -0
- package/dist/discovery/embedding-types.d.ts.map +1 -0
- package/dist/discovery/embedding-types.js +1 -0
- package/dist/discovery/embeddings.d.ts +38 -0
- package/dist/discovery/embeddings.d.ts.map +1 -0
- package/dist/discovery/embeddings.js +71 -0
- package/dist/discovery/evaluation-validation.d.ts +8 -0
- package/dist/discovery/evaluation-validation.d.ts.map +1 -0
- package/dist/discovery/evaluation-validation.js +19 -0
- package/dist/discovery/evaluation.d.ts +84 -0
- package/dist/discovery/evaluation.d.ts.map +1 -0
- package/dist/discovery/evaluation.js +310 -0
- package/dist/discovery/export.d.ts +14 -0
- package/dist/discovery/export.d.ts.map +1 -0
- package/dist/discovery/export.js +81 -0
- package/dist/discovery/facade.d.ts +82 -0
- package/dist/discovery/facade.d.ts.map +1 -0
- package/dist/discovery/facade.js +266 -0
- package/dist/discovery/fingerprint.d.ts +71 -0
- package/dist/discovery/fingerprint.d.ts.map +1 -0
- package/dist/discovery/fingerprint.js +67 -0
- package/dist/discovery/index-builder.d.ts +3 -0
- package/dist/discovery/index-builder.d.ts.map +1 -0
- package/dist/discovery/index-builder.js +129 -0
- package/dist/discovery/index-validation.d.ts +12 -0
- package/dist/discovery/index-validation.d.ts.map +1 -0
- package/dist/discovery/index-validation.js +117 -0
- package/dist/discovery/index.d.ts +32 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +18 -0
- package/dist/discovery/json-snapshot.d.ts +3 -0
- package/dist/discovery/json-snapshot.d.ts.map +1 -0
- package/dist/discovery/json-snapshot.js +11 -0
- package/dist/discovery/lexical.d.ts +12 -0
- package/dist/discovery/lexical.d.ts.map +1 -0
- package/dist/discovery/lexical.js +26 -0
- package/dist/discovery/metadata.d.ts +4 -0
- package/dist/discovery/metadata.d.ts.map +1 -0
- package/dist/discovery/metadata.js +33 -0
- package/dist/discovery/ranking.d.ts +7 -0
- package/dist/discovery/ranking.d.ts.map +1 -0
- package/dist/discovery/ranking.js +68 -0
- package/dist/discovery/replay.d.ts +50 -0
- package/dist/discovery/replay.d.ts.map +1 -0
- package/dist/discovery/replay.js +175 -0
- package/dist/discovery/retrieval-policy.d.ts +5 -0
- package/dist/discovery/retrieval-policy.d.ts.map +1 -0
- package/dist/discovery/retrieval-policy.js +10 -0
- package/dist/discovery/router.d.ts +39 -0
- package/dist/discovery/router.d.ts.map +1 -0
- package/dist/discovery/router.js +98 -0
- package/dist/discovery/selection-attempt.d.ts +20 -0
- package/dist/discovery/selection-attempt.d.ts.map +1 -0
- package/dist/discovery/selection-attempt.js +39 -0
- package/dist/discovery/selection-cards.d.ts +12 -0
- package/dist/discovery/selection-cards.d.ts.map +1 -0
- package/dist/discovery/selection-cards.js +79 -0
- package/dist/discovery/selection-configuration.d.ts +18 -0
- package/dist/discovery/selection-configuration.d.ts.map +1 -0
- package/dist/discovery/selection-configuration.js +71 -0
- package/dist/discovery/selection-response.d.ts +13 -0
- package/dist/discovery/selection-response.d.ts.map +1 -0
- package/dist/discovery/selection-response.js +47 -0
- package/dist/discovery/selection-types.d.ts +18 -0
- package/dist/discovery/selection-types.d.ts.map +1 -0
- package/dist/discovery/selection-types.js +1 -0
- package/dist/discovery/selection.d.ts +6 -0
- package/dist/discovery/selection.d.ts.map +1 -0
- package/dist/discovery/selection.js +145 -0
- package/dist/discovery/types.d.ts +291 -0
- package/dist/discovery/types.d.ts.map +1 -0
- package/dist/discovery/types.js +1 -0
- package/dist/discovery/usage.d.ts +2 -0
- package/dist/discovery/usage.d.ts.map +1 -0
- package/dist/discovery/usage.js +1 -0
- package/dist/discovery/vectors.d.ts +5 -0
- package/dist/discovery/vectors.d.ts.map +1 -0
- package/dist/discovery/vectors.js +43 -0
- package/dist/discovery/versions.d.ts +37 -0
- package/dist/discovery/versions.d.ts.map +1 -0
- package/dist/discovery/versions.js +36 -0
- package/dist/evals-live/live-eval-runner.d.ts +1 -0
- package/dist/evals-live/live-eval-runner.d.ts.map +1 -1
- package/dist/evals-live/live-eval-runner.js +12 -1
- package/dist/evals-live/types.d.ts +3 -0
- package/dist/evals-live/types.d.ts.map +1 -1
- package/dist/generated/manifest-types.d.ts +3655 -0
- package/dist/generated/manifest-types.d.ts.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +8 -0
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +11 -2
- package/dist/mcp-server/types.d.ts +4 -0
- package/dist/mcp-server/types.d.ts.map +1 -1
- package/dist/observability/redaction.d.ts.map +1 -1
- package/dist/observability/redaction.js +6 -2
- package/dist/observability/types.d.ts +1 -1
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/openai/index.d.ts +2 -0
- package/dist/openai/index.d.ts.map +1 -1
- package/dist/openai/index.js +1 -0
- package/dist/openai/responses-runner.d.ts +1 -1
- package/dist/openai/responses-runner.d.ts.map +1 -1
- package/dist/openai/responses-runner.js +134 -7
- package/dist/openai/selection-client.d.ts +28 -0
- package/dist/openai/selection-client.d.ts.map +1 -0
- package/dist/openai/selection-client.js +95 -0
- package/dist/openai/tool-loop.js +1 -1
- package/dist/openai/types.d.ts +19 -2
- package/dist/openai/types.d.ts.map +1 -1
- package/dist/replay/discovery-snapshot.d.ts +10 -0
- package/dist/replay/discovery-snapshot.d.ts.map +1 -0
- package/dist/replay/discovery-snapshot.js +17 -0
- package/dist/replay/recorder.d.ts.map +1 -1
- package/dist/replay/recorder.js +4 -0
- package/dist/replay/redaction.js +5 -1
- package/dist/replay/replayer.d.ts.map +1 -1
- package/dist/replay/replayer.js +14 -1
- package/dist/replay/types.d.ts +3 -0
- package/dist/replay/types.d.ts.map +1 -1
- package/dist/replay/validation.d.ts.map +1 -1
- package/dist/replay/validation.js +6 -0
- package/dist/runtime/bounded-json.d.ts +4 -0
- package/dist/runtime/bounded-json.d.ts.map +1 -0
- package/dist/runtime/bounded-json.js +74 -0
- package/dist/runtime/eligibility.d.ts +4 -0
- package/dist/runtime/eligibility.d.ts.map +1 -0
- package/dist/runtime/eligibility.js +128 -0
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/provider-attempt.d.ts +11 -0
- package/dist/runtime/provider-attempt.d.ts.map +1 -0
- package/dist/runtime/provider-attempt.js +221 -0
- package/dist/runtime/provider-run.d.ts +10 -0
- package/dist/runtime/provider-run.d.ts.map +1 -0
- package/dist/runtime/provider-run.js +123 -0
- package/dist/runtime/provider-stop.d.ts +9 -0
- package/dist/runtime/provider-stop.d.ts.map +1 -0
- package/dist/runtime/provider-stop.js +28 -0
- package/dist/runtime/provider-types.d.ts +189 -0
- package/dist/runtime/provider-types.d.ts.map +1 -0
- package/dist/runtime/provider-types.js +1 -0
- package/dist/runtime/provider-usage.d.ts +9 -0
- package/dist/runtime/provider-usage.d.ts.map +1 -0
- package/dist/runtime/provider-usage.js +69 -0
- package/dist/runtime/router.d.ts.map +1 -1
- package/dist/runtime/router.js +3 -118
- package/dist/runtime/types.d.ts +9 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validator.d.ts +3 -0
- package/dist/validator.d.ts.map +1 -1
- package/dist/validator.js +32 -0
- package/docs/api/public-api-policy.md +10 -0
- package/docs/api/stable-workflow.md +58 -0
- package/docs/api.md +7 -0
- package/docs/capability-manifests.md +24 -0
- package/docs/development.md +22 -0
- package/docs/discovery.md +186 -0
- package/docs/evals/discovery-live.md +199 -0
- package/docs/evals/discovery-qualification.md +158 -0
- package/docs/evals/overview.md +9 -0
- package/docs/getting-started/installation.md +11 -12
- package/docs/getting-started/openai-quickstart.md +16 -5
- package/docs/getting-started/provider-neutral-quickstart.md +247 -88
- package/docs/getting-started/quickstart.md +8 -30
- package/docs/index.md +10 -0
- package/docs/release.md +29 -5
- package/docs/runtime.md +27 -0
- package/docs/start-here.md +23 -134
- package/examples/12-capability-discovery-selection/README.md +346 -0
- package/examples/12-capability-discovery-selection/customer-cases.mjs +52 -0
- package/examples/12-capability-discovery-selection/customer-harness.mjs +91 -0
- package/examples/12-capability-discovery-selection/customer-request-example.mjs +320 -0
- package/examples/12-capability-discovery-selection/discovery-example.mjs +175 -0
- package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.suite.json +7887 -0
- package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v2.suite.json +8167 -0
- package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v3.suite.json +8342 -0
- package/examples/12-capability-discovery-selection/evaluation/application.mjs +305 -0
- package/examples/12-capability-discovery-selection/evaluation/campaign.mjs +248 -0
- package/examples/12-capability-discovery-selection/evaluation/clients.mjs +433 -0
- package/examples/12-capability-discovery-selection/evaluation/continuation.mjs +208 -0
- package/examples/12-capability-discovery-selection/evaluation/durable-budget.mjs +420 -0
- package/examples/12-capability-discovery-selection/evaluation/freeze.mjs +9 -0
- package/examples/12-capability-discovery-selection/evaluation/harness.mjs +618 -0
- package/examples/12-capability-discovery-selection/evaluation/mock-host.mjs +260 -0
- package/examples/12-capability-discovery-selection/evaluation/preflight.mjs +115 -0
- package/examples/12-capability-discovery-selection/evaluation/qualification-workflows.mjs +87 -0
- package/examples/12-capability-discovery-selection/evaluation/regrade.mjs +281 -0
- package/examples/12-capability-discovery-selection/evaluation/report.mjs +517 -0
- package/examples/12-capability-discovery-selection/evaluation/runner.mjs +613 -0
- package/examples/12-capability-discovery-selection/evaluation/saved-campaign.mjs +385 -0
- package/examples/12-capability-discovery-selection/evaluation/wording.mjs +115 -0
- package/examples/12-capability-discovery-selection/evaluation/workflow-application.mjs +378 -0
- package/examples/12-capability-discovery-selection/evaluation/workflow-cases.mjs +90 -0
- package/examples/12-capability-discovery-selection/evaluation-example.mjs +154 -0
- package/examples/12-capability-discovery-selection/hybrid-example.mjs +174 -0
- package/examples/12-capability-discovery-selection/mock-embeddings.mjs +101 -0
- package/examples/12-capability-discovery-selection/mock-selection.mjs +78 -0
- package/examples/12-capability-discovery-selection/pipeline-example.mjs +496 -0
- package/examples/12-capability-discovery-selection/retrieval-cases.mjs +31 -0
- package/examples/12-capability-discovery-selection/run-campaign-mock.mjs +40 -0
- package/examples/12-capability-discovery-selection/run-customer-mock.mjs +15 -0
- package/examples/12-capability-discovery-selection/run-evaluation-live.mjs +10 -0
- package/examples/12-capability-discovery-selection/run-evaluation-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/run-evaluation-regrade.mjs +71 -0
- package/examples/12-capability-discovery-selection/run-hybrid-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/run-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/run-pipeline-mock.mjs +2 -0
- package/examples/12-capability-discovery-selection/run-selector-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/selection-example.mjs +88 -0
- package/package.json +17 -4
- package/schemas/discovery/embedding-index.schema.json +71 -0
- package/schemas/discovery/eval-observations.schema.json +409 -0
- package/schemas/discovery/eval-suite.schema.json +375 -0
- package/schemas/discovery/metadata.schema.json +30 -0
- package/schemas/discovery/selection-response.schema.json +47 -0
- package/schemas/replay/discovery-snapshot.schema.json +335 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { toOpenAIResponsesToolName } from "ai-capability-framework";
|
|
2
|
+
import { DefaultContextBuilder } from "ai-capability-framework/runtime";
|
|
3
|
+
import {
|
|
4
|
+
createCapabilityDiscovery,
|
|
5
|
+
createInMemoryDiscoveryBudget,
|
|
6
|
+
buildCapabilitySearchCorpus,
|
|
7
|
+
buildCapabilityEmbeddingIndex,
|
|
8
|
+
DISCOVERY_STANDARD_PROFILE,
|
|
9
|
+
aggregateDiscoveryUsage
|
|
10
|
+
} from "ai-capability-framework/discovery";
|
|
11
|
+
import {
|
|
12
|
+
MockOpenAIResponsesClient,
|
|
13
|
+
mockFunctionCallResponse,
|
|
14
|
+
mockTextResponse,
|
|
15
|
+
runOpenAIResponses
|
|
16
|
+
} from "ai-capability-framework/openai";
|
|
17
|
+
import {
|
|
18
|
+
customerRequest,
|
|
19
|
+
plannedTasks,
|
|
20
|
+
taskExpectations,
|
|
21
|
+
validatePlannedTasks
|
|
22
|
+
} from "./customer-cases.mjs";
|
|
23
|
+
import { createCustomerHarness } from "./customer-harness.mjs";
|
|
24
|
+
import { DeterministicMockEmbeddingClient } from "./mock-embeddings.mjs";
|
|
25
|
+
|
|
26
|
+
function mockModel(responses) {
|
|
27
|
+
const client = new MockOpenAIResponsesClient(
|
|
28
|
+
responses.map((response) => ({
|
|
29
|
+
...response,
|
|
30
|
+
usage: { input_tokens: 20, output_tokens: 5, total_tokens: 25 }
|
|
31
|
+
}))
|
|
32
|
+
);
|
|
33
|
+
const usage = [];
|
|
34
|
+
const create = client.responses.create;
|
|
35
|
+
client.responses.create = async (input) => {
|
|
36
|
+
const response = await create(input);
|
|
37
|
+
usage.push(response.usage);
|
|
38
|
+
return response;
|
|
39
|
+
};
|
|
40
|
+
return { client, usage };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function modelUsage(reports) {
|
|
44
|
+
return {
|
|
45
|
+
mock: true,
|
|
46
|
+
inputTokens: reports.reduce((sum, report) => sum + report.input_tokens, 0),
|
|
47
|
+
outputTokens: reports.reduce((sum, report) => sum + report.output_tokens, 0),
|
|
48
|
+
totalTokens: reports.reduce((sum, report) => sum + report.total_tokens, 0),
|
|
49
|
+
tokenTotalsComplete: true,
|
|
50
|
+
estimatedCost: null,
|
|
51
|
+
costComplete: false
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function runCustomerRequestExample() {
|
|
56
|
+
const harness = await createCustomerHarness();
|
|
57
|
+
const planning = mockModel([
|
|
58
|
+
mockTextResponse(JSON.stringify(plannedTasks), "plan_example")
|
|
59
|
+
]);
|
|
60
|
+
const planResponse = await planning.client.responses.create({
|
|
61
|
+
model: "mock-model",
|
|
62
|
+
input: customerRequest,
|
|
63
|
+
instructions:
|
|
64
|
+
"Return ordered task objects with id, text and dependsOn. Use prior tasks for dependencies."
|
|
65
|
+
});
|
|
66
|
+
if (
|
|
67
|
+
typeof planResponse.output_text !== "string" ||
|
|
68
|
+
planResponse.output_text.length > 32_000
|
|
69
|
+
)
|
|
70
|
+
throw new Error("Invalid application task plan.");
|
|
71
|
+
const tasks = validatePlannedTasks(JSON.parse(planResponse.output_text));
|
|
72
|
+
const discovery = createCapabilityDiscovery({ registry: harness.registry });
|
|
73
|
+
const completed = new Map();
|
|
74
|
+
const taskResults = [],
|
|
75
|
+
ordinaryUsage = [];
|
|
76
|
+
let ordinaryModelTurns = 0;
|
|
77
|
+
|
|
78
|
+
// Application orchestration: discover one task when its dependencies are available.
|
|
79
|
+
for (const task of tasks) {
|
|
80
|
+
if (task.dependsOn.some((id) => !completed.has(id)))
|
|
81
|
+
throw new Error("Task dependency is not complete.");
|
|
82
|
+
const prior = completed.get("read-ticket");
|
|
83
|
+
const runtimeContext = {
|
|
84
|
+
...harness.runtimeContext,
|
|
85
|
+
requestId: "request_" + task.id
|
|
86
|
+
};
|
|
87
|
+
const contextBuilder = new DefaultContextBuilder({
|
|
88
|
+
hostItems: prior
|
|
89
|
+
? [
|
|
90
|
+
{
|
|
91
|
+
id: "ticket-result",
|
|
92
|
+
kind: "entity",
|
|
93
|
+
title: "Authorized ticket result",
|
|
94
|
+
data: prior.data,
|
|
95
|
+
trusted: true,
|
|
96
|
+
visibleToModel: true
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
: []
|
|
100
|
+
});
|
|
101
|
+
const [result] = await discovery.search({
|
|
102
|
+
tasks: [{ id: task.id, text: task.text }],
|
|
103
|
+
runtimeContext
|
|
104
|
+
});
|
|
105
|
+
if (result.status !== "matched")
|
|
106
|
+
throw new Error("Expected a usable shortlist for the synthetic task.");
|
|
107
|
+
const expected = taskExpectations[task.id];
|
|
108
|
+
if (
|
|
109
|
+
result.candidates.some((candidate) =>
|
|
110
|
+
expected.forbiddenIds.includes(candidate.capabilityId)
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
throw new Error("Forbidden capability in shortlist.");
|
|
114
|
+
const isRead = task.id === "read-ticket";
|
|
115
|
+
const capabilityId = isRead ? "support.ticket.get" : "support.refund.prepare_case";
|
|
116
|
+
const model = mockModel([
|
|
117
|
+
mockFunctionCallResponse({
|
|
118
|
+
name: toOpenAIResponsesToolName(capabilityId),
|
|
119
|
+
callId: "call_" + task.id,
|
|
120
|
+
args: isRead
|
|
121
|
+
? { ticket_id: "TCK-100" }
|
|
122
|
+
: {
|
|
123
|
+
ticket_id: prior.data.ticket.ticket_id,
|
|
124
|
+
order_id: prior.data.ticket.order_id,
|
|
125
|
+
reason_code: "customer_request",
|
|
126
|
+
requested_amount: null
|
|
127
|
+
}
|
|
128
|
+
}),
|
|
129
|
+
mockTextResponse(
|
|
130
|
+
isRead
|
|
131
|
+
? "Ticket TCK-100 is open for order ORD-100."
|
|
132
|
+
: "The refund case is prepared and awaiting approval."
|
|
133
|
+
)
|
|
134
|
+
]);
|
|
135
|
+
const loop = await runOpenAIResponses({
|
|
136
|
+
...harness,
|
|
137
|
+
runtimeContext,
|
|
138
|
+
contextBuilder,
|
|
139
|
+
userInput: { text: task.text },
|
|
140
|
+
router: discovery.routerFor(result),
|
|
141
|
+
client: model.client,
|
|
142
|
+
model: "mock-model"
|
|
143
|
+
});
|
|
144
|
+
const outcome = loop.toolResults[0];
|
|
145
|
+
if (
|
|
146
|
+
loop.status !== "completed" ||
|
|
147
|
+
!outcome ||
|
|
148
|
+
!["success", "prepared", "approval_required"].includes(outcome.status) ||
|
|
149
|
+
!loop.toolCalls.every((call) =>
|
|
150
|
+
expected.compatibleIds.includes(call.capabilityId)
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
throw new Error(
|
|
154
|
+
"Unexpected tool outcome: " +
|
|
155
|
+
JSON.stringify({
|
|
156
|
+
taskId: task.id,
|
|
157
|
+
status: loop.status,
|
|
158
|
+
errors: loop.errors,
|
|
159
|
+
toolStatus: outcome?.status,
|
|
160
|
+
toolErrors: outcome?.errors
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
const exportedToolNames = model.client.requests[0].tools.map((tool) => tool.name);
|
|
164
|
+
if (
|
|
165
|
+
exportedToolNames.includes(
|
|
166
|
+
toOpenAIResponsesToolName("support.refund.commit_case")
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
throw new Error("Commit must not be exported.");
|
|
170
|
+
completed.set(task.id, outcome);
|
|
171
|
+
ordinaryModelTurns += model.client.requests.length;
|
|
172
|
+
ordinaryUsage.push(...model.usage);
|
|
173
|
+
taskResults.push({
|
|
174
|
+
taskId: task.id,
|
|
175
|
+
candidateIds: result.candidates.map((candidate) => candidate.capabilityId),
|
|
176
|
+
exportedToolNames,
|
|
177
|
+
selectedCapabilityId: loop.toolCalls[0].capabilityId,
|
|
178
|
+
toolStatus: outcome.status,
|
|
179
|
+
approvalRequired:
|
|
180
|
+
outcome.action?.approvalRequired ?? outcome.status === "approval_required",
|
|
181
|
+
preview: outcome.data?.preview,
|
|
182
|
+
discoveryUsage: result.usage
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (
|
|
186
|
+
harness.readCalls.length !== 1 ||
|
|
187
|
+
harness.prepareCalls.length !== 1 ||
|
|
188
|
+
harness.prepareCalls[0].order_id !==
|
|
189
|
+
completed.get("read-ticket").data.ticket.order_id ||
|
|
190
|
+
!completed.get("prepare-refund").action?.preparedActionId ||
|
|
191
|
+
completed.get("prepare-refund").action?.approvalRequired !== true
|
|
192
|
+
)
|
|
193
|
+
throw new Error("The customer flow must read and prepare once for approval.");
|
|
194
|
+
const [noMatch] = await discovery.search({
|
|
195
|
+
tasks: [{ id: "clarify", text: "quasar" }],
|
|
196
|
+
runtimeContext: harness.runtimeContext
|
|
197
|
+
});
|
|
198
|
+
const deniedContext = structuredClone(harness.runtimeContext);
|
|
199
|
+
deniedContext.subject.permissions = [];
|
|
200
|
+
const [denied] = await discovery.search({
|
|
201
|
+
tasks: [{ id: "denied", text: "ticket" }],
|
|
202
|
+
runtimeContext: deniedContext
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
mock: true,
|
|
206
|
+
customerRequest,
|
|
207
|
+
plannedTasks: tasks,
|
|
208
|
+
taskResults,
|
|
209
|
+
planning: {
|
|
210
|
+
turns: planning.client.requests.length,
|
|
211
|
+
usage: modelUsage(planning.usage)
|
|
212
|
+
},
|
|
213
|
+
ordinaryModel: { turns: ordinaryModelTurns, usage: modelUsage(ordinaryUsage) },
|
|
214
|
+
discoveryUsage: aggregateDiscoveryUsage(
|
|
215
|
+
taskResults.flatMap((result) => result.discoveryUsage.stages),
|
|
216
|
+
taskResults.reduce(
|
|
217
|
+
(sum, result) => sum + result.discoveryUsage.totals.elapsedMs,
|
|
218
|
+
0
|
|
219
|
+
)
|
|
220
|
+
),
|
|
221
|
+
dedicatedSelectorCalls: 0,
|
|
222
|
+
embeddingAttempts: 0,
|
|
223
|
+
readCalls: harness.readCalls.length,
|
|
224
|
+
prepareCalls: harness.prepareCalls.length,
|
|
225
|
+
commitCalls: harness.executedOperations.filter(
|
|
226
|
+
(operation) => operation === "commit"
|
|
227
|
+
).length,
|
|
228
|
+
companionCases: {
|
|
229
|
+
noMatch: {
|
|
230
|
+
status: noMatch.status,
|
|
231
|
+
response: "Please clarify what you would like to do."
|
|
232
|
+
},
|
|
233
|
+
denied: {
|
|
234
|
+
status: denied.status,
|
|
235
|
+
exportedCount: discovery.routerFor(denied).route({
|
|
236
|
+
registry: harness.registry,
|
|
237
|
+
builtContext: await new DefaultContextBuilder().build({
|
|
238
|
+
baseContext: deniedContext,
|
|
239
|
+
registry: harness.registry,
|
|
240
|
+
userInput: { text: "ticket" }
|
|
241
|
+
}),
|
|
242
|
+
userInput: { text: "ticket" }
|
|
243
|
+
}).items.length
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
finalText:
|
|
247
|
+
"Ticket TCK-100 is open for order ORD-100. Its refund case is prepared and awaiting approval. No refund has been issued."
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function runSimpleHybridExample() {
|
|
252
|
+
const { registry, runtimeContext } = await createCustomerHarness();
|
|
253
|
+
const client = new DeterministicMockEmbeddingClient();
|
|
254
|
+
const budget = createInMemoryDiscoveryBudget({
|
|
255
|
+
runId: runtimeContext.runId,
|
|
256
|
+
maxProviderCalls: 2,
|
|
257
|
+
maxInputTokens: 8000
|
|
258
|
+
});
|
|
259
|
+
// Administrative build is explicit and separate from serving.
|
|
260
|
+
const built = await buildCapabilityEmbeddingIndex({
|
|
261
|
+
corpus: buildCapabilitySearchCorpus(registry),
|
|
262
|
+
client,
|
|
263
|
+
budget: budget.ledger,
|
|
264
|
+
execution: {
|
|
265
|
+
budgetPolicy: budget.policy,
|
|
266
|
+
limits: {
|
|
267
|
+
maxInputTokens: 4000,
|
|
268
|
+
maxCharactersPerText: DISCOVERY_STANDARD_PROFILE.maxCharactersPerText,
|
|
269
|
+
maxCharactersPerCall: DISCOVERY_STANDARD_PROFILE.maxCharactersPerCall
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
limits: DISCOVERY_STANDARD_PROFILE.indexLimits,
|
|
273
|
+
batchSize: 3,
|
|
274
|
+
maxProviderCalls: 1,
|
|
275
|
+
deadlineAt: Date.now() + 30_000,
|
|
276
|
+
runId: runtimeContext.runId,
|
|
277
|
+
requestId: runtimeContext.requestId
|
|
278
|
+
});
|
|
279
|
+
if (built.status !== "built")
|
|
280
|
+
throw new Error("The explicit mock build must succeed.");
|
|
281
|
+
const options = {
|
|
282
|
+
registry,
|
|
283
|
+
mode: "hybrid",
|
|
284
|
+
embedding: { client, index: built.index, maxInputTokens: 4000 }
|
|
285
|
+
};
|
|
286
|
+
const [result] = await createCapabilityDiscovery(options).search({
|
|
287
|
+
tasks: [{ id: "case", text: "case details" }],
|
|
288
|
+
runtimeContext,
|
|
289
|
+
budget
|
|
290
|
+
});
|
|
291
|
+
const before = client.calls.length;
|
|
292
|
+
const [invalid] = await createCapabilityDiscovery({
|
|
293
|
+
...options,
|
|
294
|
+
embedding: { ...options.embedding, index: {} }
|
|
295
|
+
}).search({
|
|
296
|
+
tasks: [{ id: "invalid-index", text: "ticket" }],
|
|
297
|
+
runtimeContext,
|
|
298
|
+
budget
|
|
299
|
+
});
|
|
300
|
+
if (
|
|
301
|
+
result.status !== "matched" ||
|
|
302
|
+
invalid.status !== "unavailable" ||
|
|
303
|
+
client.calls.length !== before
|
|
304
|
+
)
|
|
305
|
+
throw new Error("Hybrid contract example failed.");
|
|
306
|
+
return {
|
|
307
|
+
mock: true,
|
|
308
|
+
result,
|
|
309
|
+
invalidIndexStatus: invalid.status,
|
|
310
|
+
invalidIndexQueryCalls: client.calls.length - before,
|
|
311
|
+
embeddingAttempts: client.calls.length,
|
|
312
|
+
indexFingerprint: built.index.indexFingerprint,
|
|
313
|
+
usage: aggregateDiscoveryUsage(
|
|
314
|
+
[...built.usage.stages, ...result.usage.stages, ...invalid.usage.stages],
|
|
315
|
+
built.usage.totals.elapsedMs +
|
|
316
|
+
result.usage.totals.elapsedMs +
|
|
317
|
+
invalid.usage.totals.elapsedMs
|
|
318
|
+
)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import {
|
|
3
|
+
buildRegistry,
|
|
4
|
+
loadManifests,
|
|
5
|
+
toOpenAIResponsesToolName,
|
|
6
|
+
validateManifests
|
|
7
|
+
} from "ai-capability-framework";
|
|
8
|
+
import {
|
|
9
|
+
AicfActionLifecycleManager,
|
|
10
|
+
AicfHandlerRegistry,
|
|
11
|
+
AicfToolExecutor,
|
|
12
|
+
buildRuntimeContext,
|
|
13
|
+
DefaultContextBuilder,
|
|
14
|
+
DefaultPolicyBroker,
|
|
15
|
+
InMemoryApprovalStore,
|
|
16
|
+
InMemoryAuditSink,
|
|
17
|
+
InMemoryIdempotencyStore,
|
|
18
|
+
InMemoryPreparedActionStore,
|
|
19
|
+
StaticAuthPlatformAdapter
|
|
20
|
+
} from "ai-capability-framework/runtime";
|
|
21
|
+
import {
|
|
22
|
+
CapabilityDiscoverer,
|
|
23
|
+
RetrievalCapabilityRouter
|
|
24
|
+
} from "ai-capability-framework/discovery";
|
|
25
|
+
import {
|
|
26
|
+
MockOpenAIResponsesClient,
|
|
27
|
+
mockFunctionCallResponse,
|
|
28
|
+
mockTextResponse,
|
|
29
|
+
runOpenAIResponses
|
|
30
|
+
} from "ai-capability-framework/openai";
|
|
31
|
+
import { retrievalCases } from "./retrieval-cases.mjs";
|
|
32
|
+
|
|
33
|
+
export const discoveryConfiguration = {
|
|
34
|
+
mode: "lexical",
|
|
35
|
+
policyVersion: "example.lexical.v1",
|
|
36
|
+
limits: { maxCandidates: 2 }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export async function createDiscoveryExample() {
|
|
40
|
+
const loaded = await loadManifests({
|
|
41
|
+
path: fileURLToPath(new URL("../support/", import.meta.url))
|
|
42
|
+
});
|
|
43
|
+
if (loaded.errors.length || !validateManifests(loaded.manifests).valid) {
|
|
44
|
+
throw new Error("The synthetic support manifests must validate.");
|
|
45
|
+
}
|
|
46
|
+
const registry = buildRegistry(loaded.manifests);
|
|
47
|
+
const runtimeContext = await buildRuntimeContext({
|
|
48
|
+
adapter: new StaticAuthPlatformAdapter({
|
|
49
|
+
account: { accountId: "account_example", tenantId: "tenant_example" },
|
|
50
|
+
subject: {
|
|
51
|
+
userId: "user_example",
|
|
52
|
+
roles: ["support_agent"],
|
|
53
|
+
permissions: ["ticket.read", "refund.case.create"]
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
autonomy: {
|
|
57
|
+
autonomyTier: "A2",
|
|
58
|
+
maxRiskTier: "medium",
|
|
59
|
+
allowSideEffects: true,
|
|
60
|
+
allowExternalMessages: false,
|
|
61
|
+
allowMoneyMovement: false,
|
|
62
|
+
allowPermissionChanges: false
|
|
63
|
+
},
|
|
64
|
+
environment: "test",
|
|
65
|
+
runId: "run_example",
|
|
66
|
+
requestId: "request_example"
|
|
67
|
+
});
|
|
68
|
+
const userInput = { text: "Read ticket TCK-100." };
|
|
69
|
+
const contextBuilder = new DefaultContextBuilder();
|
|
70
|
+
const builtContext = await contextBuilder.build({
|
|
71
|
+
baseContext: runtimeContext,
|
|
72
|
+
registry,
|
|
73
|
+
userInput
|
|
74
|
+
});
|
|
75
|
+
const handlers = new AicfHandlerRegistry({ registry });
|
|
76
|
+
const readCalls = [];
|
|
77
|
+
handlers.register({
|
|
78
|
+
capabilityId: "support.ticket.get",
|
|
79
|
+
read: ({ args }) => {
|
|
80
|
+
readCalls.push(args.ticket_id);
|
|
81
|
+
return {
|
|
82
|
+
ticket: { ticket_id: args.ticket_id, status: "open", order_id: "ORD-100" }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const policyBroker = new DefaultPolicyBroker();
|
|
87
|
+
const auditSink = new InMemoryAuditSink();
|
|
88
|
+
const lifecycle = new AicfActionLifecycleManager({
|
|
89
|
+
registry,
|
|
90
|
+
handlers,
|
|
91
|
+
policyBroker,
|
|
92
|
+
auditSink,
|
|
93
|
+
approvalStore: new InMemoryApprovalStore(),
|
|
94
|
+
idempotencyStore: new InMemoryIdempotencyStore(),
|
|
95
|
+
preparedActionStore: new InMemoryPreparedActionStore()
|
|
96
|
+
});
|
|
97
|
+
const executor = new AicfToolExecutor({
|
|
98
|
+
registry,
|
|
99
|
+
handlers,
|
|
100
|
+
policyBroker,
|
|
101
|
+
auditSink,
|
|
102
|
+
actionLifecycle: lifecycle
|
|
103
|
+
});
|
|
104
|
+
const router = new RetrievalCapabilityRouter({
|
|
105
|
+
mode: "native_tool_selection",
|
|
106
|
+
discovery: discoveryConfiguration
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
registry,
|
|
110
|
+
runtimeContext,
|
|
111
|
+
builtContext,
|
|
112
|
+
contextBuilder,
|
|
113
|
+
userInput,
|
|
114
|
+
executor,
|
|
115
|
+
router,
|
|
116
|
+
readCalls
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function runDiscoveryExample() {
|
|
121
|
+
const harness = await createDiscoveryExample();
|
|
122
|
+
const routeRequest = {
|
|
123
|
+
registry: harness.registry,
|
|
124
|
+
builtContext: harness.builtContext,
|
|
125
|
+
userInput: harness.userInput
|
|
126
|
+
};
|
|
127
|
+
const batch = await new CapabilityDiscoverer(discoveryConfiguration).discover({
|
|
128
|
+
routeRequest,
|
|
129
|
+
runId: harness.runtimeContext.runId,
|
|
130
|
+
requestId: harness.runtimeContext.requestId,
|
|
131
|
+
queries: retrievalCases.map(({ id, fragments }) => ({ id, fragments }))
|
|
132
|
+
});
|
|
133
|
+
for (const [index, result] of batch.entries()) {
|
|
134
|
+
const fixture = retrievalCases[index];
|
|
135
|
+
const ids = result.candidates.map((candidate) => candidate.capabilityId);
|
|
136
|
+
if (
|
|
137
|
+
ids.some(
|
|
138
|
+
(id) => !fixture.compatibleIds.includes(id) || fixture.forbiddenIds.includes(id)
|
|
139
|
+
) ||
|
|
140
|
+
fixture.compatibleIds.some((id) => !ids.includes(id))
|
|
141
|
+
) {
|
|
142
|
+
throw new Error("Retrieval fixture failed: " + fixture.id);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const routed = await harness.router.routeWithDiagnostics(routeRequest);
|
|
146
|
+
const client = new MockOpenAIResponsesClient([
|
|
147
|
+
mockFunctionCallResponse({
|
|
148
|
+
name: toOpenAIResponsesToolName("support.ticket.get"),
|
|
149
|
+
args: { ticket_id: "TCK-100" },
|
|
150
|
+
callId: "call_example"
|
|
151
|
+
}),
|
|
152
|
+
mockTextResponse("Ticket TCK-100 is open.", "response_example")
|
|
153
|
+
]);
|
|
154
|
+
const loop = await runOpenAIResponses({
|
|
155
|
+
...harness,
|
|
156
|
+
client,
|
|
157
|
+
model: "mock-model"
|
|
158
|
+
});
|
|
159
|
+
if (loop.status !== "completed" || loop.toolResults[0]?.status !== "success") {
|
|
160
|
+
throw new Error("The mocked ordinary model/tool loop must complete.");
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
fixtureResults: batch.map(({ queryId, status, candidates }) => ({
|
|
164
|
+
queryId,
|
|
165
|
+
status,
|
|
166
|
+
capabilityIds: candidates.map((candidate) => candidate.capabilityId)
|
|
167
|
+
})),
|
|
168
|
+
slice: routed.slice,
|
|
169
|
+
exportedToolNames: client.requests[0].tools.map((tool) => tool.name),
|
|
170
|
+
discoveryUsage: routed.discovery[0].usage,
|
|
171
|
+
ordinaryMockModelTurns: client.requests.length,
|
|
172
|
+
toolStatus: loop.toolResults[0].status,
|
|
173
|
+
finalText: loop.finalText
|
|
174
|
+
};
|
|
175
|
+
}
|