@x12i/ai-profiles 3.1.0 → 3.2.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/README.md +108 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Resolve AI profile names into concrete model choices. The package ships JSON in `src/data/` as the offline fallback. By default, registry data is fetched from public assets, validated, and cached in memory.
|
|
4
4
|
|
|
5
|
-
> **v3.
|
|
5
|
+
> **v3.2** adds **`normalizeInvokeModel`**, **`resolveAndNormalizeInvokeModel`**, and **`resolveAndNormalizeBundledInvokeModel`** for gateway invoke wire shape. Downstream repos should pin **`"@x12i/ai-profiles": "^3.2.0"`**.
|
|
6
|
+
>
|
|
7
|
+
> **v3.0** removes legacy resolution inputs: shortcuts, bare profiles, and aliases. Only four resolution forms are accepted — `profile/choice`, vendor slug, gateway wire, and `{ provider, modelId }`. See [CHANGELOG.md](CHANGELOG.md).
|
|
6
8
|
>
|
|
7
9
|
> **v2.0** (still relevant): every `resolveAIProfile` call must pass `catalogLane` (`"text"`, `"image"`, …). Profile keys `deepseek` and `summarization` are gone — use `vol` and `sum`.
|
|
8
10
|
|
|
@@ -35,6 +37,36 @@ Every profile has a **`defaultChoice`** (recommended) and typically **two altern
|
|
|
35
37
|
|
|
36
38
|
Tasks should declare **requirements** as explicit `profile/choice` keys (e.g. `cheap/default`), not model names. Use discovery APIs (`listAIProfiles`, `resolveAIProfileByTags`) to pick a profile when the caller does not already have a composite key.
|
|
37
39
|
|
|
40
|
+
### Graph and stored model config
|
|
41
|
+
|
|
42
|
+
Graph JSON, studio defaults, and run overrides store **profile slot strings only** — never concrete provider model ids. Each task uses a complete triple:
|
|
43
|
+
|
|
44
|
+
| Slot | Example | Phase |
|
|
45
|
+
|------|---------|-------|
|
|
46
|
+
| `preActionModel` | `deep/default` | PRE (xynthesis / planning) |
|
|
47
|
+
| `skillModel` | `balanced/default` | MAIN (skill gateway) |
|
|
48
|
+
| `postActionModel` | `cheap/default` | POST (xynthesis / wrap-up) |
|
|
49
|
+
|
|
50
|
+
Canonical storage form is **`profile/choice`** (separator `/`). Use **`formatProfileChoiceKey(profile, choice)`** when writing slots and **`isKnownProfileChoice(input)`** (or **`matchStrictProfileInput`**) before persist or execute. Always include the choice segment — even for the profile default (`cheap/default`, not bare `cheap`).
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
"modelConfig": {
|
|
54
|
+
"cases": [
|
|
55
|
+
{
|
|
56
|
+
"modelConfig": {
|
|
57
|
+
"preActionModel": "cyber/default",
|
|
58
|
+
"skillModel": "audit/default",
|
|
59
|
+
"postActionModel": "cheap/default"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**At execute time**, hosts pass the stored alias triple unchanged; each phase resolves independently via `resolveAIProfile` (or `resolveAndNormalizeInvokeModel` at the gateway boundary). Do **not** pre-resolve aliases to concrete ids in graph JSON, `runtime.modelConfig`, or `aliasConfig`.
|
|
67
|
+
|
|
68
|
+
**Forbidden in stored config:** legacy keys (`xynthesisModel`, `postStrategyModel`), legacy aliases (`default`, `strong`, `weak`), bare profile keys (`cheap`), concrete slugs (`openai/gpt-4o-mini`), and legacy `@` composites (`cheap@anthropic_cheap` — migrate to `cheap/anthropic_cheap` on save).
|
|
69
|
+
|
|
38
70
|
### Profile catalog (bundled)
|
|
39
71
|
|
|
40
72
|
Profiles are grouped by **what kind of work** they represent—not only by price/quality tier. Several axes are orthogonal (structured output, agents, research, local inference).
|
|
@@ -213,7 +245,11 @@ npm install @x12i/ai-profiles
|
|
|
213
245
|
| `loadBundledModelsCatalog()` | Yes | Package-shipped `models-catalog.json` only |
|
|
214
246
|
| `loadBundledModelsRegistry()` | Yes | Package-shipped `models-registry.json` (linkage index) |
|
|
215
247
|
| `listAIProfileChoices(options?)` | No (async) | List explicit `profile/choice` keys for strict pickers and stored config |
|
|
248
|
+
| `formatProfileChoiceKey(profile, choice)` | Yes | Build canonical slot key (`cheap/default`) |
|
|
249
|
+
| `parseProfileChoiceKey(input)` | Yes | Parse `profile/choice` (accepts `\` as separator on input) |
|
|
250
|
+
| `matchStrictProfileInput(registry, input)` | Yes | Resolve input to profile+choice or `invalidChoice` |
|
|
216
251
|
| `isKnownProfileChoice(input)` | Yes (bundled only) | Whether input is a known valid `profile/choice` key |
|
|
252
|
+
| `isKnownProfileChoiceInput(registry, input)` | Yes | Same as above with an explicit registry |
|
|
217
253
|
| `getBundledRegistry()` | Yes | Sync bundled profiles registry (no network) |
|
|
218
254
|
| `resolveBundledAIProfile(input, options)` | Yes | Strict `profile/choice` → model, pricing, runtime (`source: "bundled"`) |
|
|
219
255
|
| `resolveBundledInput(input, options)` | Yes | Unified resolver: `profile/choice` → registry; vendor slug / gateway wire / `{ provider, modelId }` → catalog |
|
|
@@ -230,7 +266,7 @@ npm install @x12i/ai-profiles
|
|
|
230
266
|
| `listProfileSecondaries(profileKey, registry)` | Yes | List per-choice `secondary` declarations on a profile |
|
|
231
267
|
| `resolveAIProfileByTags(tags, options?)` | No (async) | Pick best profile by tag overlap (partial match OK), then resolve |
|
|
232
268
|
| `scoreProfileTags` / `rankProfilesByTags` | Yes | Tag overlap scoring for discovery and custom UIs |
|
|
233
|
-
| `AIProfilesError` | — | Typed errors (`UNKNOWN_PROFILE`, …) |
|
|
269
|
+
| `AIProfilesError` / `AIProfilesErrorCode` | — | Typed errors (`UNKNOWN_PROFILE`, `INVOKE_MODEL_ALIAS_AT_GATEWAY`, `INVOKE_MODEL_SHAPE_INVALID`, …) |
|
|
234
270
|
|
|
235
271
|
### Model identity (vendor + model strings)
|
|
236
272
|
|
|
@@ -437,40 +473,65 @@ ensureConcreteModel("google/gemini-2.5-flash-lite");
|
|
|
437
473
|
|
|
438
474
|
## Invoke wire shape (gateway / HTTP)
|
|
439
475
|
|
|
440
|
-
Downstream packages (`ai-tasks`, `ai-skills`, `ai-gateway`, `xynthesis`, `optimixer`) must agree on what is sent on the wire **after** semantic resolution
|
|
476
|
+
Downstream packages (`ai-tasks`, `ai-skills`, `ai-gateway`, `xynthesis`, `optimixer`) must agree on what is sent on the wire **after** semantic resolution. **Stored graph config** uses `profile/choice` aliases; **gateway / HTTP** uses the invoke tuple below.
|
|
477
|
+
|
|
478
|
+
### Display slug
|
|
479
|
+
|
|
480
|
+
A **display slug** is a single string with an `openrouter/` prefix for human-readable UI and logs, e.g. `openrouter/deepseek/deepseek-v4-pro`. Studio pickers, plan previews, and Activix run logs may show this form. It is **not** the shape passed to `ai-gateway` catalog lookup or OpenRouter HTTP — normalize before those boundaries.
|
|
441
481
|
|
|
442
|
-
|
|
443
|
-
|-------|---------|-----|
|
|
444
|
-
| **Display slug** | `openrouter/deepseek/deepseek-v4-pro` | Studio UI, plan logs, human preview only |
|
|
445
|
-
| **Invoke tuple** | `{ provider: "openrouter", model: "deepseek/deepseek-v4-pro" }` | `ai-gateway` catalog lookup, OpenRouter HTTP, funcx PRE/POST |
|
|
482
|
+
### Invoke tuple
|
|
446
483
|
|
|
447
|
-
|
|
484
|
+
An **invoke tuple** is `{ provider, model, routing }` where `provider` is `"openrouter"` (or a vendor for direct routing) and `model` is the **bare** vendor/model slug without a gateway prefix, e.g. `{ provider: "openrouter", model: "deepseek/deepseek-v4-pro", routing: "openrouter" }`. This is what `ai-gateway` uses for catalog lookup and what OpenRouter HTTP expects.
|
|
485
|
+
|
|
486
|
+
Call **`normalizeInvokeModel`** at every gateway boundary (idempotent). Profile intent still goes through **`resolveAIProfile`** first, or use **`resolveAndNormalizeInvokeModel`** / **`resolveAndNormalizeBundledInvokeModel`** for the combined path.
|
|
487
|
+
|
|
488
|
+
| Export | Sync? | Purpose |
|
|
489
|
+
|--------|-------|---------|
|
|
490
|
+
| `normalizeInvokeModel(input, options?)` | Yes | Display slug or invoke object → canonical invoke tuple; rejects unresolved `profile/choice` |
|
|
491
|
+
| `resolveAndNormalizeInvokeModel(alias, options)` | No (async) | `resolveAIProfile` → `normalizeInvokeModel` |
|
|
492
|
+
| `resolveAndNormalizeBundledInvokeModel(alias, options)` | Yes | Bundled `resolveAIProfile` → `normalizeInvokeModel` (no network) |
|
|
448
493
|
|
|
449
494
|
```ts
|
|
450
495
|
import {
|
|
451
496
|
normalizeInvokeModel,
|
|
452
497
|
resolveAndNormalizeInvokeModel,
|
|
498
|
+
resolveAndNormalizeBundledInvokeModel,
|
|
453
499
|
} from "@x12i/ai-profiles";
|
|
454
500
|
|
|
455
|
-
// Display slug → invoke tuple (
|
|
501
|
+
// Display slug → invoke tuple (string or { model } object)
|
|
456
502
|
normalizeInvokeModel("openrouter/deepseek/deepseek-v4-pro");
|
|
503
|
+
normalizeInvokeModel({ model: "openrouter/deepseek/deepseek-v4-pro" });
|
|
457
504
|
// → { provider: "openrouter", model: "deepseek/deepseek-v4-pro", routing: "openrouter", displaySlug: "..." }
|
|
458
505
|
|
|
459
|
-
// Already-correct tuple → unchanged
|
|
506
|
+
// Already-correct tuple → unchanged (idempotent)
|
|
460
507
|
normalizeInvokeModel({ provider: "openrouter", model: "deepseek/deepseek-v4-pro" });
|
|
461
508
|
|
|
462
|
-
//
|
|
463
|
-
normalizeInvokeModel("
|
|
509
|
+
// Unresolved profile/choice at gateway → INVOKE_MODEL_ALIAS_AT_GATEWAY
|
|
510
|
+
normalizeInvokeModel("cheap/default");
|
|
511
|
+
normalizeInvokeModel({ model: "cheap/default" }); // same error
|
|
464
512
|
|
|
465
|
-
// Profile/choice → resolve, then normalize
|
|
513
|
+
// Profile/choice → resolve, then normalize (gateway catalog-lookup tuple)
|
|
466
514
|
await resolveAndNormalizeInvokeModel("cyber/deep_forensics", {
|
|
467
515
|
catalogLane: "text",
|
|
468
516
|
useOpenRouter: true,
|
|
469
517
|
});
|
|
470
|
-
// → { provider: "openrouter", model: "deepseek/deepseek-v4-pro", ... }
|
|
518
|
+
// → { provider: "openrouter", model: "deepseek/deepseek-v4-pro", routing: "openrouter", ... }
|
|
519
|
+
|
|
520
|
+
// Sync bundled variant (tests, predict paths)
|
|
521
|
+
resolveAndNormalizeBundledInvokeModel("cyber/deep_forensics", {
|
|
522
|
+
catalogLane: "text",
|
|
523
|
+
useOpenRouter: true,
|
|
524
|
+
});
|
|
471
525
|
```
|
|
472
526
|
|
|
473
|
-
|
|
527
|
+
**Error codes** (on `AIProfilesError.code`):
|
|
528
|
+
|
|
529
|
+
| Code | When |
|
|
530
|
+
|------|------|
|
|
531
|
+
| `INVOKE_MODEL_ALIAS_AT_GATEWAY` | `profile/choice` reached `normalizeInvokeModel` without prior `resolveAIProfile` |
|
|
532
|
+
| `INVOKE_MODEL_SHAPE_INVALID` | Empty model, missing provider, or unparseable composite after normalization |
|
|
533
|
+
|
|
534
|
+
`allowAlias: true` exists for rare internal paths; **gateway and HTTP callers should not use it** — resolve aliases first.
|
|
474
535
|
|
|
475
536
|
## Sync validation (bundled registry)
|
|
476
537
|
|
|
@@ -531,14 +592,18 @@ This applies to any `profile/choice` input that resolves to a remote profile cho
|
|
|
531
592
|
|
|
532
593
|
### Avoid hardcoded OpenRouter slugs (especially dated previews)
|
|
533
594
|
|
|
534
|
-
Do **not** copy OpenRouter slugs into app config (`preActionModel`, env vars, etc.) and do **not** use `canonicalSlug` from the catalog for API calls. OpenRouter retires dated preview ids (e.g. `google/gemini-2.5-flash-lite-preview-06-17`) while stable ids remain (`google/gemini-2.5-flash-lite`).
|
|
595
|
+
Do **not** copy OpenRouter slugs into graph JSON or app config (`preActionModel`, `skillModel`, `postActionModel`, env vars, etc.) and do **not** use `canonicalSlug` from the catalog for API calls. OpenRouter retires dated preview ids (e.g. `google/gemini-2.5-flash-lite-preview-06-17`) while stable ids remain (`google/gemini-2.5-flash-lite`).
|
|
535
596
|
|
|
536
597
|
```ts
|
|
537
|
-
// Good — profile
|
|
598
|
+
// Good — store profile/choice; resolve at execute time
|
|
599
|
+
preActionModel: "cheap/default";
|
|
600
|
+
skillModel: "balanced/default";
|
|
601
|
+
postActionModel: "cheap/default";
|
|
602
|
+
|
|
538
603
|
const pre = await resolveAIProfile("cheap/default", { catalogLane: "text" });
|
|
539
|
-
|
|
604
|
+
// pre.modelId → "google/gemini-2.5-flash-lite" (concrete id only after resolution)
|
|
540
605
|
|
|
541
|
-
// Bad —
|
|
606
|
+
// Bad — concrete slug in stored config (stale when catalog moves)
|
|
542
607
|
preActionModel: "google/gemini-2.5-flash-lite-preview-06-17";
|
|
543
608
|
```
|
|
544
609
|
|
|
@@ -649,29 +714,26 @@ The `version` field in the JSON files (and `registryVersion` on resolved output)
|
|
|
649
714
|
## Integration example
|
|
650
715
|
|
|
651
716
|
```ts
|
|
652
|
-
|
|
653
|
-
node.taskConfiguration.aiProfile ?? "balanced/default",
|
|
654
|
-
{ catalogLane: "text" },
|
|
655
|
-
);
|
|
717
|
+
import { resolveAIProfile, resolveAndNormalizeInvokeModel } from "@x12i/ai-profiles";
|
|
656
718
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
backend: resolved.backend,
|
|
663
|
-
temperature: resolved.runtime.temperature,
|
|
664
|
-
outputMode: resolved.runtime.outputMode,
|
|
665
|
-
toolPolicy: resolved.runtime.toolPolicy,
|
|
666
|
-
reasoningEffort: resolved.runtime.reasoningEffort,
|
|
719
|
+
// Graph JSON stores aliases only (merged job defaults + node overrides per task)
|
|
720
|
+
const slots = {
|
|
721
|
+
preActionModel: node.taskConfiguration.modelConfig?.preActionModel ?? "deep/default",
|
|
722
|
+
skillModel: node.taskConfiguration.modelConfig?.skillModel ?? "balanced/default",
|
|
723
|
+
postActionModel: node.taskConfiguration.modelConfig?.postActionModel ?? "cheap/default",
|
|
667
724
|
};
|
|
668
725
|
|
|
669
|
-
//
|
|
670
|
-
|
|
726
|
+
// Resolve each phase independently at execute time
|
|
727
|
+
const pre = await resolveAIProfile(slots.preActionModel, { catalogLane: "text" });
|
|
728
|
+
const main = await resolveAIProfile(slots.skillModel, { catalogLane: "text" });
|
|
729
|
+
const post = await resolveAIProfile(slots.postActionModel, { catalogLane: "text" });
|
|
671
730
|
|
|
672
|
-
//
|
|
673
|
-
|
|
674
|
-
|
|
731
|
+
// Gateway handoff: resolve + normalize invoke tuple
|
|
732
|
+
const mainInvoke = await resolveAndNormalizeInvokeModel(slots.skillModel, {
|
|
733
|
+
catalogLane: "text",
|
|
734
|
+
useOpenRouter: true,
|
|
735
|
+
});
|
|
736
|
+
// mainInvoke → { provider: "openrouter", model: "openai/gpt-5.4", routing: "openrouter", ... }
|
|
675
737
|
```
|
|
676
738
|
|
|
677
739
|
## Profile matrix (FuncX contract)
|
|
@@ -690,7 +752,7 @@ Default choice per profile (vendor-native ids in registry; OpenRouter slugs when
|
|
|
690
752
|
| `research` | Remote | **`reasoning`** | `default` | `claude-sonnet-4.6` |
|
|
691
753
|
| `cyber` | Remote | **`reasoning`** | `default` | `claude-sonnet-4.6` |
|
|
692
754
|
| `content` | Remote | **`default`** | `default` | `claude-sonnet-4.6` |
|
|
693
|
-
| `
|
|
755
|
+
| `sum` | Remote | **`default`** | `default` | `claude-haiku-4.5` |
|
|
694
756
|
| `vision` | Remote | **`default`** | `default` | `gpt-5.4` |
|
|
695
757
|
| `local` | CPU (no OpenRouter) | **`cpu`** | `llama_cpp_gguf` | `funcx:llama-cpp:gguf` |
|
|
696
758
|
| `local` | CPU | **`cpu`** | `transformersjs_default` | `Xenova/distilbart-cnn-6-6` |
|
|
@@ -858,7 +920,13 @@ Workflow YAML is in [`docs/github-workflows/`](docs/github-workflows/). Copy int
|
|
|
858
920
|
Consumers install with:
|
|
859
921
|
|
|
860
922
|
```bash
|
|
861
|
-
npm install @x12i/ai-profiles
|
|
923
|
+
npm install @x12i/ai-profiles@^3.2.0
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
Peer dependency for downstream repos (graphs-studio, graph-engine, ai-tasks):
|
|
927
|
+
|
|
928
|
+
```json
|
|
929
|
+
"@x12i/ai-profiles": "^3.2.0"
|
|
862
930
|
```
|
|
863
931
|
|
|
864
932
|
## License
|
package/package.json
CHANGED