@harperfast/harper 5.3.0-beta.3 → 5.3.0-beta.4
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/bin/cliOperations.ts +25 -3
- package/bin/run.ts +18 -0
- package/components/Application.ts +52 -78
- package/components/DESIGN.md +76 -3
- package/components/Scope.ts +10 -4
- package/components/anthropic/index.ts +10 -1
- package/components/bedrock/index.ts +10 -1
- package/components/componentLoader.ts +23 -14
- package/components/mcp/tools/operations.ts +3 -1
- package/components/ollama/index.ts +10 -1
- package/components/openai/index.ts +265 -18
- package/components/operations.js +40 -1
- package/config/configUtils.ts +3 -19
- package/dataLayer/DESIGN.md +29 -5
- package/dataLayer/harperBridge/ResourceBridge.ts +39 -3
- package/dataLayer/restoreMarker.ts +250 -14
- package/dataLayer/schema.ts +2 -7
- package/dataLayer/schemaDescribe.ts +10 -0
- package/dist/bin/cliOperations.js +28 -3
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/run.js +19 -0
- package/dist/bin/run.js.map +1 -1
- package/dist/components/Application.d.ts +8 -2
- package/dist/components/Application.js +43 -69
- package/dist/components/Application.js.map +1 -1
- package/dist/components/Scope.d.ts +1 -1
- package/dist/components/Scope.js +9 -4
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +0 -10
- package/dist/components/anthropic/index.js +9 -1
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +0 -12
- package/dist/components/bedrock/index.js +9 -1
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.js +23 -14
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/mcp/tools/operations.js +1 -1
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/ollama/index.d.ts +0 -13
- package/dist/components/ollama/index.js +9 -1
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +11 -15
- package/dist/components/openai/index.js +224 -12
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +9 -0
- package/dist/components/operations.js +32 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/config/configUtils.d.ts +1 -2
- package/dist/config/configUtils.js +7 -22
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +29 -2
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +25 -3
- package/dist/dataLayer/restoreMarker.js +213 -13
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/dataLayer/schema.js +1 -4
- package/dist/dataLayer/schema.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +10 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +28 -0
- package/dist/resources/DatabaseTransaction.d.ts +21 -1
- package/dist/resources/DatabaseTransaction.js +147 -8
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +1 -1
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/Table.d.ts +31 -3
- package/dist/resources/Table.js +397 -79
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/auditStore.js +16 -10
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/blob.d.ts +1 -0
- package/dist/resources/blob.js +14 -5
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/branchDatabase.js +3 -3
- package/dist/resources/branchDatabase.js.map +1 -1
- package/dist/resources/databaseDropPreparation.d.ts +20 -0
- package/dist/resources/databaseDropPreparation.js +106 -0
- package/dist/resources/databaseDropPreparation.js.map +1 -0
- package/dist/resources/databases.d.ts +26 -21
- package/dist/resources/databases.js +1040 -204
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +23 -0
- package/dist/resources/derivedIndexRegistry.js +92 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -1
- package/dist/resources/derivedIndexRuntime.d.ts +10 -0
- package/dist/resources/derivedIndexRuntime.js +136 -25
- package/dist/resources/derivedIndexRuntime.js.map +1 -1
- package/dist/resources/derivedIndexes.d.ts +39 -0
- package/dist/resources/derivedIndexes.js +733 -0
- package/dist/resources/derivedIndexes.js.map +1 -0
- package/dist/resources/fullTextSchema.d.ts +8 -0
- package/dist/resources/fullTextSchema.js +42 -0
- package/dist/resources/fullTextSchema.js.map +1 -1
- package/dist/resources/fullTextSchemaLifecycle.d.ts +0 -5
- package/dist/resources/fullTextSchemaLifecycle.js +0 -9
- package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
- package/dist/resources/graphql.js +101 -9
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +0 -10
- package/dist/resources/indexes/hnswDerivedIndex.js +0 -230
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -1
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +3 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +57 -8
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
- package/dist/resources/models/Models.d.ts +26 -3
- package/dist/resources/models/Models.js +256 -13
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/TestBackend.d.ts +3 -1
- package/dist/resources/models/TestBackend.js +35 -1
- package/dist/resources/models/TestBackend.js.map +1 -1
- package/dist/resources/models/analyticsTable.d.ts +2 -2
- package/dist/resources/models/analyticsTable.js +4 -2
- package/dist/resources/models/analyticsTable.js.map +1 -1
- package/dist/resources/models/backendHelpers.d.ts +24 -0
- package/dist/resources/models/backendHelpers.js +18 -1
- package/dist/resources/models/backendHelpers.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +10 -21
- package/dist/resources/models/backendRegistry.js +74 -18
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +7 -0
- package/dist/resources/models/bootstrap.js +36 -4
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/models/decideHook.d.ts +33 -0
- package/dist/resources/models/decideHook.js +105 -0
- package/dist/resources/models/decideHook.js.map +1 -0
- package/dist/resources/models/decision.d.ts +64 -0
- package/dist/resources/models/decision.js +507 -0
- package/dist/resources/models/decision.js.map +1 -0
- package/dist/resources/models/decisionStore.d.ts +134 -0
- package/dist/resources/models/decisionStore.js +350 -0
- package/dist/resources/models/decisionStore.js.map +1 -0
- package/dist/resources/models/embedHook.d.ts +66 -8
- package/dist/resources/models/embedHook.js +228 -61
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/generativeDecision.d.ts +58 -0
- package/dist/resources/models/generativeDecision.js +315 -0
- package/dist/resources/models/generativeDecision.js.map +1 -0
- package/dist/resources/models/types.d.ts +232 -4
- package/dist/security/authn/oidc/tokenExchange.js +3 -33
- package/dist/security/authn/oidc/tokenExchange.js.map +1 -1
- package/dist/security/authn/oidc/tokenUseTable.d.ts +9 -0
- package/dist/security/authn/oidc/tokenUseTable.js +28 -0
- package/dist/security/authn/oidc/tokenUseTable.js.map +1 -0
- package/dist/security/certificateVerification/certificateVerificationSource.d.ts +0 -1
- package/dist/security/certificateVerification/certificateVerificationSource.js +3 -3
- package/dist/security/certificateVerification/certificateVerificationSource.js.map +1 -1
- package/dist/security/certificateVerification/crlVerification.js +77 -165
- package/dist/security/certificateVerification/crlVerification.js.map +1 -1
- package/dist/security/certificateVerification/types.d.ts +0 -3
- package/dist/security/certificateVerification/verificationTables.d.ts +19 -0
- package/dist/security/certificateVerification/verificationTables.js +125 -0
- package/dist/security/certificateVerification/verificationTables.js.map +1 -0
- package/dist/security/certificateVerification/verificationUtils.d.ts +1 -1
- package/dist/security/certificateVerification/verificationUtils.js +6 -31
- package/dist/security/certificateVerification/verificationUtils.js.map +1 -1
- package/dist/security/jsLoader.d.ts +5 -0
- package/dist/security/jsLoader.js +8 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/Server.d.ts +1 -0
- package/dist/server/Server.js.map +1 -1
- package/dist/server/http.js +2 -2
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +21 -2
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobOwnership.d.ts +25 -0
- package/dist/server/jobs/jobOwnership.js +127 -0
- package/dist/server/jobs/jobOwnership.js.map +1 -0
- package/dist/server/jobs/jobProcess.js +9 -2
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobs.js +18 -5
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/loadRootComponents.js +10 -0
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +11 -1
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +12 -2
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/threads/itc.d.ts +4 -1
- package/dist/server/threads/itc.js +29 -6
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +12 -4
- package/dist/server/threads/manageThreads.js +241 -119
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/threadServer.d.ts +1 -0
- package/dist/server/threads/threadServer.js +15 -1
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/server/threads/windowsProcessTree.d.ts +91 -0
- package/dist/server/threads/windowsProcessTree.js +361 -0
- package/dist/server/threads/windowsProcessTree.js.map +1 -0
- package/dist/server/throttle.d.ts +1 -1
- package/dist/server/throttle.js +8 -1
- package/dist/server/throttle.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.js +53 -46
- package/dist/upgrade/directives/5-3-0.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +10 -1
- package/dist/utility/errors/hdbError.js +55 -11
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/fsync.d.ts +2 -0
- package/dist/utility/fsync.js +20 -0
- package/dist/utility/fsync.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +18 -0
- package/dist/utility/hdbTerms.d.ts +3 -0
- package/dist/utility/hdbTerms.js +3 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/signalling.d.ts +7 -1
- package/dist/utility/signalling.js +27 -3
- package/dist/utility/signalling.js.map +1 -1
- package/dist/utility/when.d.ts +1 -0
- package/dist/utility/when.js +21 -0
- package/dist/utility/when.js.map +1 -1
- package/dist/validation/configValidator.js +30 -3
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +13 -0
- package/json/systemSchema.json +28 -0
- package/npm-shrinkwrap.json +151 -55
- package/package.json +14 -13
- package/resources/DESIGN.md +80 -100
- package/resources/DatabaseTransaction.ts +157 -10
- package/resources/PrimaryRocksDatabase.ts +2 -2
- package/resources/Table.ts +494 -101
- package/resources/auditStore.ts +18 -10
- package/resources/blob.ts +13 -6
- package/resources/branchDatabase.ts +3 -3
- package/resources/databaseDropPreparation.ts +120 -0
- package/resources/databases.ts +1155 -205
- package/resources/derivedIndexRegistry.ts +122 -0
- package/resources/derivedIndexRuntime.ts +157 -30
- package/resources/derivedIndexes.ts +856 -0
- package/resources/fullTextSchema.ts +55 -0
- package/resources/fullTextSchemaLifecycle.ts +0 -15
- package/resources/graphql.ts +107 -12
- package/resources/indexes/DESIGN.md +7 -0
- package/resources/indexes/hnswDerivedIndex.ts +0 -260
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +72 -9
- package/resources/models/DESIGN.md +39 -0
- package/resources/models/Models.ts +361 -18
- package/resources/models/TestBackend.ts +56 -1
- package/resources/models/analyticsTable.ts +6 -4
- package/resources/models/backendHelpers.ts +17 -0
- package/resources/models/backendRegistry.ts +76 -21
- package/resources/models/bootstrap.ts +57 -7
- package/resources/models/decideHook.ts +135 -0
- package/resources/models/decision.ts +505 -0
- package/resources/models/decisionStore.ts +419 -0
- package/resources/models/embedHook.ts +272 -55
- package/resources/models/generativeDecision.ts +459 -0
- package/resources/models/types.ts +231 -4
- package/security/DESIGN.md +22 -2
- package/security/authn/oidc/tokenExchange.ts +6 -34
- package/security/authn/oidc/tokenUseTable.ts +27 -0
- package/security/certificateVerification/certificateVerificationSource.ts +2 -3
- package/security/certificateVerification/crlVerification.ts +96 -174
- package/security/certificateVerification/types.ts +0 -3
- package/security/certificateVerification/verificationTables.ts +124 -0
- package/security/certificateVerification/verificationUtils.ts +8 -32
- package/security/jsLoader.ts +9 -1
- package/server/DESIGN.md +27 -1
- package/server/Server.ts +2 -1
- package/server/http.ts +4 -2
- package/server/itc/serverHandlers.js +29 -1
- package/server/jobs/jobOwnership.ts +89 -0
- package/server/jobs/jobProcess.ts +8 -2
- package/server/jobs/jobs.ts +15 -4
- package/server/loadRootComponents.js +11 -0
- package/server/serverHelpers/contentTypes.ts +13 -1
- package/server/serverHelpers/serverHandlers.js +11 -2
- package/server/threads/itc.js +32 -6
- package/server/threads/manageThreads.js +268 -122
- package/server/threads/threadServer.js +13 -1
- package/server/threads/windowsProcessTree.ts +409 -0
- package/server/throttle.ts +11 -1
- package/studio/web/assets/{Chat-CveGbxe6.js → Chat-Bq60AO-g.js} +1 -1
- package/studio/web/assets/{FloatingChat-IpCFgYWL.js → FloatingChat-D4560T0p.js} +3 -3
- package/studio/web/assets/{apiToken-CS8fFTgL.js → apiToken-BEvbFPMc.js} +1 -1
- package/studio/web/assets/{applications-D7F4Jopl.js → applications-CSUVTH4E.js} +1 -1
- package/studio/web/assets/{cssMode-Daw_oLPq.js → cssMode-DJyc9mHj.js} +1 -1
- package/studio/web/assets/{editor-B3Bs1zWX.js → editor-DDIsQfj6.js} +1 -1
- package/studio/web/assets/{html-CDaQwWiE.js → html-g1BJHxLy.js} +1 -1
- package/studio/web/assets/{htmlMode-Cb6emO4U.js → htmlMode-Vh5IglCQ.js} +1 -1
- package/studio/web/assets/{index-B4g2Fu_2.js → index-DGRAzvO4.js} +5 -5
- package/studio/web/assets/{index.lazy-NwhvF1q1.js → index.lazy-vgSic7et.js} +1 -1
- package/studio/web/assets/{javascript-2_86gB9Q.js → javascript-5VIolqf7.js} +1 -1
- package/studio/web/assets/{jsonMode-DX-i86uQ.js → jsonMode-DG4zQraI.js} +1 -1
- package/studio/web/assets/{languageServices-B85WN-Ej.js → languageServices-t5FBpX-i.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-JCWHIsKg.js → lspLanguageFeatures-DgVnNPh-.js} +1 -1
- package/studio/web/assets/{notifications-D9ejzt52.js → notifications-BeWcX04m.js} +1 -1
- package/studio/web/assets/{notifications-B7hjKhai.js → notifications-DUGWrWoO.js} +1 -1
- package/studio/web/assets/{profile-D8B6No3F.js → profile-BvUEDBzW.js} +1 -1
- package/studio/web/assets/{regions-DFazIfTK.js → regions-CB-_2m7M.js} +1 -1
- package/studio/web/assets/{register-CwFLviLF.js → register-1w1Zr3b2.js} +2 -2
- package/studio/web/assets/{setComponentFile-VnJ80SSJ.js → setComponentFile-z4-qJCbA.js} +1 -1
- package/studio/web/assets/{setup-BYQxFy-C.js → setup-CS50uJ4x.js} +2 -2
- package/studio/web/assets/{status-Cn5XjmsL.js → status-CI0wOdOE.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-g9xIh6TX.js → toggleHighContrast-BsAE4jPe.js} +1 -1
- package/studio/web/assets/{tsMode-EC-n0Wqc.js → tsMode-D83Gg3cq.js} +1 -1
- package/studio/web/assets/{typescript-DHmM9JCO.js → typescript-Dfp-GrNq.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-BUdnVSVv.js → useEntityRestURL-BKetlINf.js} +1 -1
- package/studio/web/assets/{workers-BVl9bBB-.js → workers-CCvmDLH3.js} +1 -1
- package/studio/web/assets/{xml-C4_sqOkX.js → xml-06pZp00U.js} +1 -1
- package/studio/web/assets/{yaml-CVkbDqtW.js → yaml-iSnWMVlV.js} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-3-0.ts +61 -54
- package/utility/DESIGN.md +5 -1
- package/utility/errors/hdbError.ts +56 -8
- package/utility/fsync.ts +16 -0
- package/utility/hdbTerms.ts +3 -0
- package/utility/signalling.ts +33 -4
- package/utility/when.ts +22 -0
- package/validation/configValidator.ts +36 -7
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in `decision` backend over a configured generative logical name (#2779). A call either
|
|
3
|
+
* scores every allowed value from the generative model's own likelihoods (#2838), when that name
|
|
4
|
+
* routes to a backend implementing `scoreChoices`, or asks the model for `samples`
|
|
5
|
+
* structured-output completions and reports the vote frequencies as the distribution. Configured
|
|
6
|
+
* as `models.decision.<name>: { backend: generative, scoring: auto | vote | score }`.
|
|
7
|
+
*
|
|
8
|
+
* Every sample and every scoring call flows through the `models` facade, so each one writes its
|
|
9
|
+
* own `hdb_model_calls` row and token metrics; the decide row itself reports no usage, or the
|
|
10
|
+
* tokens would be counted twice (the same reason the agent loop's outer call writes no row).
|
|
11
|
+
*/
|
|
12
|
+
import { ServerError } from '../../utility/errors/hdbError.ts';
|
|
13
|
+
import { composeSignal } from './backendHelpers.ts';
|
|
14
|
+
import { setDecision } from './backendRegistry.ts';
|
|
15
|
+
import {
|
|
16
|
+
allowedValues,
|
|
17
|
+
isObjectSchema,
|
|
18
|
+
parseDecisionSample,
|
|
19
|
+
type SamplePair,
|
|
20
|
+
stateToText,
|
|
21
|
+
toResponseSchema,
|
|
22
|
+
wantsNoMatch,
|
|
23
|
+
} from './decision.ts';
|
|
24
|
+
import { models, scoringLimit } from './Models.ts';
|
|
25
|
+
import { getRouter } from './routing.ts';
|
|
26
|
+
import type {
|
|
27
|
+
BackendOpts,
|
|
28
|
+
ChoiceScores,
|
|
29
|
+
Capability,
|
|
30
|
+
DecideInput,
|
|
31
|
+
DecideOpts,
|
|
32
|
+
DecisionLeaf,
|
|
33
|
+
DecisionOutcome,
|
|
34
|
+
DecisionOutput,
|
|
35
|
+
DecisionSchema,
|
|
36
|
+
GenerateInput,
|
|
37
|
+
GenerateOpts,
|
|
38
|
+
GenerateResult,
|
|
39
|
+
ModelBackend,
|
|
40
|
+
ModelCallResult,
|
|
41
|
+
ModelCapabilities,
|
|
42
|
+
ModelKind,
|
|
43
|
+
ScoreChoicesOpts,
|
|
44
|
+
} from './types.ts';
|
|
45
|
+
|
|
46
|
+
export type ScoringMode = 'auto' | 'vote' | 'score';
|
|
47
|
+
|
|
48
|
+
export interface GenerativeDecisionConfig {
|
|
49
|
+
/** Logical name of the generative model to sample or score; default `'default'`. */
|
|
50
|
+
generative?: string;
|
|
51
|
+
/** Completions per voted decision (1..25); default 5. */
|
|
52
|
+
samples?: number;
|
|
53
|
+
/** Completions or scoring calls in flight at once (1..25); default 5. */
|
|
54
|
+
concurrency?: number;
|
|
55
|
+
temperature?: number;
|
|
56
|
+
/** Budget for the whole decision, composed with the caller's signal. */
|
|
57
|
+
requestTimeoutMs?: number;
|
|
58
|
+
/**
|
|
59
|
+
* `auto` (default) scores when the generative name routes to a backend that implements
|
|
60
|
+
* `scoreChoices` and votes otherwise, also when scoring proves unsupported for a call; `score`
|
|
61
|
+
* never votes; `vote` never scores, which makes it the rollback setting.
|
|
62
|
+
*/
|
|
63
|
+
scoring?: ScoringMode;
|
|
64
|
+
/**
|
|
65
|
+
* Route each sample only to a generative candidate that sends the response schema as a
|
|
66
|
+
* decoding constraint (`structuredOutput`); default `true`. `false` samples prompt-only
|
|
67
|
+
* backends, whose replies are parsed leniently and fail the decision when no in-schema
|
|
68
|
+
* answer is found.
|
|
69
|
+
*/
|
|
70
|
+
requireStructuredOutput?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const DEFAULT_SAMPLES = 5;
|
|
74
|
+
export const MAX_SAMPLES = 25;
|
|
75
|
+
const DEFAULT_CONCURRENCY = 5;
|
|
76
|
+
const STRUCTURED_OUTPUT: readonly Capability[] = Object.freeze(['structuredOutput'] as Capability[]);
|
|
77
|
+
|
|
78
|
+
const CAPABILITIES: ModelCapabilities = Object.freeze({
|
|
79
|
+
embed: false,
|
|
80
|
+
generate: false,
|
|
81
|
+
stream: false,
|
|
82
|
+
tools: false,
|
|
83
|
+
adapters: false,
|
|
84
|
+
decide: true,
|
|
85
|
+
calibrated: false,
|
|
86
|
+
noMatch: true,
|
|
87
|
+
calibratedNoMatch: false,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const VOTE_SYSTEM_PROMPT =
|
|
91
|
+
'You make a decision about the input. Reply with a single JSON object that matches the required schema, choosing only from the allowed values.';
|
|
92
|
+
const SCORE_SYSTEM_PROMPT = 'You make a decision about the input, choosing only from the allowed values.';
|
|
93
|
+
const SCORE_NO_MATCH_SYSTEM_PROMPT =
|
|
94
|
+
'You make a decision about the input. Choose the allowed value that fits it, or the final option when none of the allowed values fits.';
|
|
95
|
+
const NO_MATCH_CHOICE = '(none of the listed values)';
|
|
96
|
+
|
|
97
|
+
export type GenerateFn = (input: GenerateInput, opts: GenerateOpts) => Promise<GenerateResult>;
|
|
98
|
+
export type ScoreFn = (
|
|
99
|
+
input: GenerateInput,
|
|
100
|
+
choices: readonly string[],
|
|
101
|
+
opts: ScoreChoicesOpts
|
|
102
|
+
) => Promise<ChoiceScores>;
|
|
103
|
+
|
|
104
|
+
/** The facade calls the adapter makes; injectable for tests. */
|
|
105
|
+
export interface GenerativeDecisionDeps {
|
|
106
|
+
generate?: GenerateFn;
|
|
107
|
+
score?: ScoreFn;
|
|
108
|
+
/** Whether the generative name currently routes to a backend that scores; `auto` asks before every decision. */
|
|
109
|
+
canScore?: (logicalName: string, largestLeaf: number) => boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export class GenerativeDecisionError extends ServerError {
|
|
113
|
+
constructor(message: string) {
|
|
114
|
+
super(message);
|
|
115
|
+
this.name = 'GenerativeDecisionError';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function registerGenerativeDecisionBackend(args: {
|
|
120
|
+
logicalName: string;
|
|
121
|
+
kind: ModelKind;
|
|
122
|
+
config: GenerativeDecisionConfig;
|
|
123
|
+
}): void {
|
|
124
|
+
if (args.kind !== 'decision')
|
|
125
|
+
throw new GenerativeDecisionError(`backend 'generative' serves models.decision entries, not models.${args.kind}`);
|
|
126
|
+
setDecision(args.logicalName, createGenerativeDecisionBackend(args.config));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function createGenerativeDecisionBackend(
|
|
130
|
+
config: GenerativeDecisionConfig = {},
|
|
131
|
+
deps: GenerativeDecisionDeps = {}
|
|
132
|
+
): ModelBackend {
|
|
133
|
+
const generate = deps.generate ?? ((input, opts) => models.generate(input, opts));
|
|
134
|
+
const score = deps.score ?? ((input, choices, opts) => models.scoreChoices(input, choices, opts));
|
|
135
|
+
const canScore = deps.canScore ?? routesToScorer;
|
|
136
|
+
const logicalName = config.generative ?? 'default';
|
|
137
|
+
const samples = boundedCount(config.samples, DEFAULT_SAMPLES);
|
|
138
|
+
const concurrency = boundedCount(config.concurrency, DEFAULT_CONCURRENCY);
|
|
139
|
+
const scoring = config.scoring ?? 'auto';
|
|
140
|
+
const { temperature, requestTimeoutMs } = config;
|
|
141
|
+
|
|
142
|
+
const vote = async (
|
|
143
|
+
state: DecideInput,
|
|
144
|
+
schema: DecisionSchema,
|
|
145
|
+
instructions: string | undefined,
|
|
146
|
+
signal: AbortSignal | undefined
|
|
147
|
+
): Promise<DecisionOutput<unknown>> => {
|
|
148
|
+
const input = buildInput(
|
|
149
|
+
state,
|
|
150
|
+
schema,
|
|
151
|
+
instructions,
|
|
152
|
+
wantsNoMatch(schema) ? `${VOTE_SYSTEM_PROMPT} ${voteNoMatchLine(schema)}` : VOTE_SYSTEM_PROMPT,
|
|
153
|
+
undefined
|
|
154
|
+
);
|
|
155
|
+
const responseFormat = { schema: toResponseSchema(schema) };
|
|
156
|
+
const votes = await runPool(
|
|
157
|
+
Array.from({ length: samples }, () => async (sampleSignal: AbortSignal) => {
|
|
158
|
+
const result = await generate(input, {
|
|
159
|
+
model: logicalName,
|
|
160
|
+
responseFormat,
|
|
161
|
+
temperature,
|
|
162
|
+
signal: sampleSignal,
|
|
163
|
+
requires,
|
|
164
|
+
});
|
|
165
|
+
return parseSample(schema, result.content, logicalName);
|
|
166
|
+
}),
|
|
167
|
+
concurrency,
|
|
168
|
+
signal
|
|
169
|
+
);
|
|
170
|
+
return tally(schema, votes);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const scoreLeaf = async (
|
|
174
|
+
state: DecideInput,
|
|
175
|
+
schema: DecisionSchema,
|
|
176
|
+
field: string | undefined,
|
|
177
|
+
leaf: DecisionLeaf,
|
|
178
|
+
instructions: string | undefined,
|
|
179
|
+
signal: AbortSignal
|
|
180
|
+
): Promise<LeafScore> => {
|
|
181
|
+
const values = allowedValues(leaf);
|
|
182
|
+
const choices = values.map((value) => String(value));
|
|
183
|
+
if (leaf.noMatch !== true) {
|
|
184
|
+
const input = buildInput(state, schema, instructions, SCORE_SYSTEM_PROMPT, field);
|
|
185
|
+
const scored = await score(input, choices, { model: logicalName, signal });
|
|
186
|
+
return { distribution: softmax(values, scored?.logLikelihoods, logicalName) };
|
|
187
|
+
}
|
|
188
|
+
const input = buildInput(state, schema, instructions, SCORE_NO_MATCH_SYSTEM_PROMPT, field);
|
|
189
|
+
const scored = await score(input, [...choices, noMatchChoice(choices)], { model: logicalName, signal });
|
|
190
|
+
const all = softmax([...values, NO_MATCH], scored?.logLikelihoods, logicalName);
|
|
191
|
+
// Normalized over the allowed values' own scores, not by dividing out the none share, which can underflow to zero.
|
|
192
|
+
const distribution = softmax(values, (scored!.logLikelihoods as number[]).slice(0, values.length), logicalName);
|
|
193
|
+
return { distribution, noMatch: all[values.length].probability };
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// One scoring call per leaf, through the same pool and budget as voting, so a 32-field schema
|
|
197
|
+
// does not open 32 provider connections at once and a caller abort settles every sibling.
|
|
198
|
+
const scoreAll = async (
|
|
199
|
+
state: DecideInput,
|
|
200
|
+
schema: DecisionSchema,
|
|
201
|
+
instructions: string | undefined,
|
|
202
|
+
signal: AbortSignal | undefined
|
|
203
|
+
): Promise<DecisionOutput<unknown>> => {
|
|
204
|
+
if (!isObjectSchema(schema)) {
|
|
205
|
+
const [scored] = await runPool(
|
|
206
|
+
[(leafSignal: AbortSignal) => scoreLeaf(state, schema, undefined, schema, instructions, leafSignal)],
|
|
207
|
+
concurrency,
|
|
208
|
+
signal
|
|
209
|
+
);
|
|
210
|
+
return scored;
|
|
211
|
+
}
|
|
212
|
+
const entries = Object.entries(schema.properties);
|
|
213
|
+
const marginals = await runPool(
|
|
214
|
+
entries.map(
|
|
215
|
+
([name, leaf]) =>
|
|
216
|
+
(leafSignal: AbortSignal) =>
|
|
217
|
+
scoreLeaf(state, schema, name, leaf, instructions, leafSignal)
|
|
218
|
+
),
|
|
219
|
+
concurrency,
|
|
220
|
+
signal
|
|
221
|
+
);
|
|
222
|
+
return { fields: Object.fromEntries(entries.map(([name], i) => [name, marginals[i]])) };
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const signatureFor = (mode: ScoringMode) =>
|
|
226
|
+
`generative=${logicalName};mode=${mode};samples=${samples};temperature=${temperature ?? 'default'}`;
|
|
227
|
+
|
|
228
|
+
const requires = config.requireStructuredOutput === false ? undefined : (STRUCTURED_OUTPUT as Capability[]);
|
|
229
|
+
return {
|
|
230
|
+
name: 'generative',
|
|
231
|
+
capabilities: () => CAPABILITIES,
|
|
232
|
+
async decide(
|
|
233
|
+
state: DecideInput,
|
|
234
|
+
schema: DecisionSchema,
|
|
235
|
+
opts: BackendOpts<DecideOpts>
|
|
236
|
+
): Promise<ModelCallResult<DecisionOutput<unknown>>> {
|
|
237
|
+
const signal = composeSignal(opts.signal, requestTimeoutMs);
|
|
238
|
+
const mode: ScoringMode =
|
|
239
|
+
scoring === 'auto' ? (canScore(logicalName, largestLeaf(schema)) ? 'score' : 'vote') : scoring;
|
|
240
|
+
if (mode === 'score') {
|
|
241
|
+
try {
|
|
242
|
+
return {
|
|
243
|
+
status: 'completed',
|
|
244
|
+
output: { ...(await scoreAll(state, schema, opts.instructions, signal)), signature: signatureFor('score') },
|
|
245
|
+
};
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if (scoring !== 'auto' || !isScoringUnsupported(err)) throw err;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
status: 'completed',
|
|
252
|
+
output: { ...(await vote(state, schema, opts.instructions, signal)), signature: signatureFor('vote') },
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// A candidate that advertises scoring without implementing it counts as able, so the facade reports
|
|
259
|
+
// that contract failure instead of the decision quietly voting.
|
|
260
|
+
function routesToScorer(logicalName: string, largestLeafSize: number): boolean {
|
|
261
|
+
for (const backend of getRouter().route({ kind: 'generative', logicalName, requires: ['scoreChoices'] })) {
|
|
262
|
+
const limit = scoringLimit(backend.capabilities());
|
|
263
|
+
if (limit === undefined || largestLeafSize <= limit || typeof backend.scoreChoices !== 'function') return true;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Infinity for anything that is not a well-formed leaf, so an unexpected shape never reads as within a limit.
|
|
269
|
+
function leafSize(leaf: DecisionLeaf): number {
|
|
270
|
+
if (!leaf || typeof leaf !== 'object') return Infinity;
|
|
271
|
+
if (Array.isArray((leaf as { enum?: unknown }).enum)) return (leaf as { enum: unknown[] }).enum.length;
|
|
272
|
+
if ((leaf as { type?: unknown }).type === 'boolean') return 2;
|
|
273
|
+
const { type, minimum, maximum } = leaf as { type?: unknown; minimum?: unknown; maximum?: unknown };
|
|
274
|
+
if (type !== 'integer' || !Number.isSafeInteger(minimum) || !Number.isSafeInteger(maximum)) return Infinity;
|
|
275
|
+
const size = (maximum as number) - (minimum as number) + 1;
|
|
276
|
+
return size > 0 ? size : Infinity;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// A leaf that opted into no-match is scored with one extra choice, the "none" continuation.
|
|
280
|
+
function scoredChoices(leaf: DecisionLeaf): number {
|
|
281
|
+
return leafSize(leaf) + ((leaf as { noMatch?: unknown } | undefined)?.noMatch === true ? 1 : 0);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function largestLeaf(schema: DecisionSchema): number {
|
|
285
|
+
if (!isObjectSchema(schema)) return scoredChoices(schema);
|
|
286
|
+
let largest = 0;
|
|
287
|
+
for (const name in schema.properties)
|
|
288
|
+
if (Object.hasOwn(schema.properties, name)) largest = Math.max(largest, scoredChoices(schema.properties[name]));
|
|
289
|
+
return largest;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** The errors after which `auto` votes: the backend declined this call, or lost the capability since the probe. */
|
|
293
|
+
function isScoringUnsupported(err: unknown): boolean {
|
|
294
|
+
const name = (err as { name?: string } | null)?.name;
|
|
295
|
+
return name === 'ChoiceScoringUnsupportedError' || name === 'ModelCapabilityError';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Run `tasks` with at most `concurrency` in flight under one AbortController. The first failure
|
|
300
|
+
* aborts the rest, and the pool settles before that failure is thrown, so no task rejects
|
|
301
|
+
* unobserved and the facade never falls back to another candidate while calls are in flight.
|
|
302
|
+
* Siblings are aborted with a fresh reason, not the failure itself: a call that was cut short is
|
|
303
|
+
* recorded as aborted, not as a second copy of the failure with the same billed usage.
|
|
304
|
+
*/
|
|
305
|
+
async function runPool<T>(
|
|
306
|
+
tasks: ReadonlyArray<(signal: AbortSignal) => Promise<T>>,
|
|
307
|
+
concurrency: number,
|
|
308
|
+
signal: AbortSignal | undefined
|
|
309
|
+
): Promise<T[]> {
|
|
310
|
+
const controller = new AbortController();
|
|
311
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
312
|
+
if (signal?.aborted) onAbort();
|
|
313
|
+
else signal?.addEventListener('abort', onAbort, { once: true });
|
|
314
|
+
const results: T[] = new Array(tasks.length);
|
|
315
|
+
let failure: unknown;
|
|
316
|
+
let failed = false;
|
|
317
|
+
let next = 0;
|
|
318
|
+
const worker = async (): Promise<void> => {
|
|
319
|
+
while (next < tasks.length && !controller.signal.aborted) {
|
|
320
|
+
const index = next++;
|
|
321
|
+
try {
|
|
322
|
+
results[index] = await tasks[index](controller.signal);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
if (!failed) {
|
|
325
|
+
failed = true;
|
|
326
|
+
failure = err;
|
|
327
|
+
}
|
|
328
|
+
controller.abort(new DOMException('another call in this decision failed', 'AbortError'));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
try {
|
|
334
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length) }, worker));
|
|
335
|
+
} finally {
|
|
336
|
+
signal?.removeEventListener('abort', onAbort);
|
|
337
|
+
}
|
|
338
|
+
if (failed) throw failure;
|
|
339
|
+
signal?.throwIfAborted();
|
|
340
|
+
return results;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function boundedCount(value: number | undefined, fallback: number): number {
|
|
344
|
+
if (!Number.isInteger(value) || (value as number) < 1) return fallback;
|
|
345
|
+
return Math.min(value as number, MAX_SAMPLES);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function parseSample(schema: DecisionSchema, content: string, logicalName: string): unknown {
|
|
349
|
+
try {
|
|
350
|
+
return parseDecisionSample(schema, content);
|
|
351
|
+
} catch (err) {
|
|
352
|
+
throw new GenerativeDecisionError(
|
|
353
|
+
`generative model '${logicalName}' produced a sample outside the decision schema: ${(err as Error).message}`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Normalize the backend's log-likelihoods over the leaf's values; the shape is checked because no backend's arithmetic is trusted. */
|
|
359
|
+
function softmax(values: readonly unknown[], logLikelihoods: unknown, logicalName: string): DecisionOutcome[] {
|
|
360
|
+
if (
|
|
361
|
+
!Array.isArray(logLikelihoods) ||
|
|
362
|
+
logLikelihoods.length !== values.length ||
|
|
363
|
+
!logLikelihoods.every((x) => typeof x === 'number' && Number.isFinite(x))
|
|
364
|
+
)
|
|
365
|
+
throw new GenerativeDecisionError(
|
|
366
|
+
`generative model '${logicalName}' did not return one finite log-likelihood per allowed value (${values.length})`
|
|
367
|
+
);
|
|
368
|
+
const scores = logLikelihoods as number[];
|
|
369
|
+
const max = Math.max(...scores);
|
|
370
|
+
const weights = scores.map((x) => Math.exp(x - max));
|
|
371
|
+
const total = weights.reduce((sum, w) => sum + w, 0);
|
|
372
|
+
return values.map((value, i) => ({ value, probability: weights[i] / total }));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function buildInput(
|
|
376
|
+
state: DecideInput,
|
|
377
|
+
schema: DecisionSchema,
|
|
378
|
+
instructions: string | undefined,
|
|
379
|
+
system: string,
|
|
380
|
+
field: string | undefined
|
|
381
|
+
): GenerateInput {
|
|
382
|
+
const lines: string[] = [];
|
|
383
|
+
if (instructions) lines.push(instructions, '');
|
|
384
|
+
if (schema.description) lines.push(schema.description, '');
|
|
385
|
+
if (!isObjectSchema(schema)) {
|
|
386
|
+
lines.push(`Decide "value": ${describeLeaf(schema)}`);
|
|
387
|
+
} else if (field !== undefined) {
|
|
388
|
+
lines.push(`Decide "${field}": ${describeLeaf(schema.properties[field])}`);
|
|
389
|
+
} else {
|
|
390
|
+
lines.push('Decide each field:');
|
|
391
|
+
for (const [name, leaf] of Object.entries(schema.properties)) lines.push(`- ${name}: ${describeLeaf(leaf)}`);
|
|
392
|
+
}
|
|
393
|
+
lines.push('', 'Input:', stateToText(state));
|
|
394
|
+
return { system, messages: [{ role: 'user', content: lines.join('\n') }] };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function describeLeaf(leaf: DecisionLeaf): string {
|
|
398
|
+
const range =
|
|
399
|
+
!('enum' in leaf) && leaf.type === 'integer'
|
|
400
|
+
? `an integer from ${leaf.minimum} to ${leaf.maximum}`
|
|
401
|
+
: `one of ${allowedValues(leaf)
|
|
402
|
+
.map((value) => JSON.stringify(value))
|
|
403
|
+
.join(', ')}`;
|
|
404
|
+
return leaf.description ? `${leaf.description} (${range})` : range;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function tally(schema: DecisionSchema, votes: unknown[]): DecisionOutput<unknown> {
|
|
408
|
+
if (isObjectSchema(schema)) {
|
|
409
|
+
const fields: Record<string, DecisionOutput<unknown>> = {};
|
|
410
|
+
for (const [name, leaf] of Object.entries(schema.properties))
|
|
411
|
+
fields[name] = tallyLeaf(
|
|
412
|
+
leaf,
|
|
413
|
+
votes.map((vote) => (vote as Record<string, unknown>)[name])
|
|
414
|
+
);
|
|
415
|
+
return { fields };
|
|
416
|
+
}
|
|
417
|
+
return tallyLeaf(schema, votes);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function tallyLeaf(leaf: DecisionLeaf, votes: unknown[]): DecisionOutput<unknown> {
|
|
421
|
+
if (leaf.noMatch !== true) return { distribution: frequencies(leaf, votes) };
|
|
422
|
+
const values: unknown[] = new Array(votes.length);
|
|
423
|
+
let none = 0;
|
|
424
|
+
for (let i = 0; i < votes.length; i++) {
|
|
425
|
+
const pair = votes[i] as SamplePair;
|
|
426
|
+
values[i] = pair.value;
|
|
427
|
+
if (pair.noMatch) none++;
|
|
428
|
+
}
|
|
429
|
+
return { distribution: frequencies(leaf, values), noMatch: none / votes.length };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const NO_MATCH = Symbol('noMatch');
|
|
433
|
+
|
|
434
|
+
interface LeafScore {
|
|
435
|
+
distribution: DecisionOutcome[];
|
|
436
|
+
noMatch?: number;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function voteNoMatchLine(schema: DecisionSchema): string {
|
|
440
|
+
const rule =
|
|
441
|
+
'give the closest allowed value as "value", and set "noMatch" to true only when none of the allowed values truly fits the input.';
|
|
442
|
+
if (!isObjectSchema(schema)) return `Answer as { "value", "noMatch" }: ${rule}`;
|
|
443
|
+
const names = Object.entries(schema.properties)
|
|
444
|
+
.filter(([, leaf]) => leaf.noMatch === true)
|
|
445
|
+
.map(([name]) => JSON.stringify(name));
|
|
446
|
+
return `Answer ${names.join(', ')} each as { "value", "noMatch" }: ${rule}`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function noMatchChoice(choices: readonly string[]): string {
|
|
450
|
+
let text = NO_MATCH_CHOICE;
|
|
451
|
+
while (choices.includes(text)) text = `(${text})`;
|
|
452
|
+
return text;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function frequencies(leaf: DecisionLeaf, votes: unknown[]): DecisionOutcome[] {
|
|
456
|
+
const counts = new Map<unknown, number>();
|
|
457
|
+
for (const vote of votes) counts.set(vote, (counts.get(vote) ?? 0) + 1);
|
|
458
|
+
return allowedValues(leaf).map((value) => ({ value, probability: (counts.get(value) ?? 0) / votes.length }));
|
|
459
|
+
}
|