runbios-sdk 0.2.22-dev.313 → 0.2.22-dev.319
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 +48 -1
- package/dist/client.d.ts +11 -0
- package/dist/client.js +28 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/resources/inference.d.ts +68 -2
- package/dist/resources/inference.js +70 -4
- package/dist/resources/loop.d.ts +59 -66
- package/dist/resources/loop.js +108 -13
- package/dist/types.d.ts +153 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,7 +100,7 @@ without an explicit replay acknowledgement from the endpoint.
|
|
|
100
100
|
|
|
101
101
|
**Parameter naming on `/v1` calls.** Only the parameters this SDK names are
|
|
102
102
|
camelCase: `model`, `messages`, `tools`, `toolChoice`, `reasoningEffort`,
|
|
103
|
-
`inferenceKey`, `idempotencyKey`, `requestId`, `signal`. Every other key is
|
|
103
|
+
`inferenceKey`, `idempotencyKey`, `requestId`, `signal`, `loop`. Every other key is
|
|
104
104
|
forwarded to the OpenAI-compatible endpoint **verbatim**, so it must use
|
|
105
105
|
OpenAI's own snake_case spelling — `max_tokens`, `top_p`, `stop`,
|
|
106
106
|
`frequency_penalty`, `presence_penalty`. The endpoint rejects an unknown key
|
|
@@ -217,6 +217,53 @@ for await (const event of dedicated.streamCompletions({ model: 'completion-deplo
|
|
|
217
217
|
methods close the upstream reader when iteration ends. None of the inference
|
|
218
218
|
POSTs are retried automatically, and `idempotencyKey` is sent only if supplied.
|
|
219
219
|
|
|
220
|
+
#### Record conversations into a Conscious Loop pipeline (one header)
|
|
221
|
+
|
|
222
|
+
Add `loop: { pipeline: '<tag>' }` to `chatCompletions`, `streamChatCompletions`,
|
|
223
|
+
`messages` or `streamMessages` and the call is sent exactly as before with one
|
|
224
|
+
more header, `X-Loop-Pipeline: <tag>` (several tags: `pipeline: ['a', 'b']`).
|
|
225
|
+
The gateway records the exchange -- what was asked, what the model answered and
|
|
226
|
+
its tool calls -- as a sample of the pipelines those tags feed (a pipeline's
|
|
227
|
+
`own_tag`), even where recording is off for that model: the header is your
|
|
228
|
+
consent for that one exchange. It works for serverless models and dedicated
|
|
229
|
+
deployments, including the pipeline's own `loop-<tag>` model.
|
|
230
|
+
|
|
231
|
+
The gateway answers with the sample's id in `X-Loop-Sample-Id`. A non-streaming
|
|
232
|
+
result carries it as `loop_sample_id` (non-enumerable: it is never part of the
|
|
233
|
+
answer's JSON), and `loop.onSampleId` hears it as soon as the headers arrive,
|
|
234
|
+
before the first streamed chunk. It is `null` when the gateway recorded nothing.
|
|
235
|
+
Send feedback on that id; it is the trace id the feedback calls take.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const reply = await client.inference.chatCompletions({
|
|
239
|
+
model: 'loop-support-replies',
|
|
240
|
+
messages: [{ role: 'user', content: 'Where is my order?' }],
|
|
241
|
+
loop: { pipeline: 'support-replies' },
|
|
242
|
+
});
|
|
243
|
+
const sampleId = reply.loop_sample_id;
|
|
244
|
+
if (sampleId) {
|
|
245
|
+
await client.loop.upvote(sampleId); // a good answer
|
|
246
|
+
// await client.loop.downvote(sampleId); // a bad one
|
|
247
|
+
// await client.loop.correct(sampleId, 'The right answer.');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
for await (const chunk of client.inference.streamChatCompletions({
|
|
251
|
+
model: 'loop-support-replies',
|
|
252
|
+
messages: [{ role: 'user', content: 'Where is my order?' }],
|
|
253
|
+
loop: { pipeline: 'support-replies', onSampleId: (id) => console.log('sample', id) },
|
|
254
|
+
})) {
|
|
255
|
+
process.stdout.write(String((chunk.choices as any)?.[0]?.delta?.content ?? ''));
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Feedback sent before the sample is recorded is refused `409
|
|
260
|
+
SAMPLE_NOT_YET_RECORDED`, and nothing is stored. `upvote`, `downvote`,
|
|
261
|
+
`correct`, `gold` and `signal` send it again after the `Retry-After` seconds (2
|
|
262
|
+
when none is given), at most 6 times in all, then throw that error. A stream
|
|
263
|
+
that was cut short is never recorded, so its id stays unknown. If you send
|
|
264
|
+
`X-Request-ID`, keep it unique per call: a repeated one is recorded once.
|
|
265
|
+
`completions`, `embeddings` and `rerank` are not recorded and refuse the option.
|
|
266
|
+
|
|
220
267
|
#### Workspace serverless usage and limits (read-only)
|
|
221
268
|
|
|
222
269
|
A workspace-bound platform key or hosted OAuth grant with `analytics:read` can
|
package/dist/client.d.ts
CHANGED
|
@@ -50,8 +50,19 @@ export declare class ApiError extends Error {
|
|
|
50
50
|
readonly availableGpus: AvailableGpuAlternative[] | undefined;
|
|
51
51
|
/** Availability snapshot time behind an availability rejection. */
|
|
52
52
|
readonly checkedAt: string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* How many seconds the service asked you to wait before trying again: its
|
|
55
|
+
* `Retry-After` header, as seconds or as a date. Undefined when it sent none.
|
|
56
|
+
*/
|
|
57
|
+
retryAfter: number | undefined;
|
|
53
58
|
constructor(status: number, body: ApiErrorBody);
|
|
54
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Seconds to wait from a `Retry-After` header: a number of seconds, or an HTTP
|
|
62
|
+
* date (seconds until then, never negative). Undefined when absent or
|
|
63
|
+
* unreadable. @internal
|
|
64
|
+
*/
|
|
65
|
+
export declare function retryAfterSeconds(value: string | null | undefined, now?: number): number | undefined;
|
|
55
66
|
/**
|
|
56
67
|
* The one transient GPU-rejection code: the GPU is not in stock right now, so
|
|
57
68
|
* waiting or joining the capacity queue can still succeed.
|
package/dist/client.js
CHANGED
|
@@ -53,6 +53,11 @@ export class ApiError extends Error {
|
|
|
53
53
|
availableGpus;
|
|
54
54
|
/** Availability snapshot time behind an availability rejection. */
|
|
55
55
|
checkedAt;
|
|
56
|
+
/**
|
|
57
|
+
* How many seconds the service asked you to wait before trying again: its
|
|
58
|
+
* `Retry-After` header, as seconds or as a date. Undefined when it sent none.
|
|
59
|
+
*/
|
|
60
|
+
retryAfter;
|
|
56
61
|
constructor(status, body) {
|
|
57
62
|
const err = body.error;
|
|
58
63
|
const nested = typeof err === 'object' && err !== null ? err : null;
|
|
@@ -71,8 +76,29 @@ export class ApiError extends Error {
|
|
|
71
76
|
const alts = flat.available_gpus ?? flat.available_alternatives;
|
|
72
77
|
this.availableGpus = Array.isArray(alts) && alts.length > 0 ? alts : undefined;
|
|
73
78
|
this.checkedAt = flat.checked_at;
|
|
79
|
+
this.retryAfter = undefined;
|
|
74
80
|
}
|
|
75
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Seconds to wait from a `Retry-After` header: a number of seconds, or an HTTP
|
|
84
|
+
* date (seconds until then, never negative). Undefined when absent or
|
|
85
|
+
* unreadable. @internal
|
|
86
|
+
*/
|
|
87
|
+
export function retryAfterSeconds(value, now = Date.now()) {
|
|
88
|
+
const raw = (value ?? '').trim();
|
|
89
|
+
if (!raw)
|
|
90
|
+
return undefined;
|
|
91
|
+
if (/^\d+(\.\d+)?$/.test(raw))
|
|
92
|
+
return Number(raw);
|
|
93
|
+
const at = Date.parse(raw);
|
|
94
|
+
return Number.isNaN(at) ? undefined : Math.max(0, (at - now) / 1000);
|
|
95
|
+
}
|
|
96
|
+
/** The error a non-2xx response stands for, with its `Retry-After`. @internal */
|
|
97
|
+
function errorFromResponse(res, data) {
|
|
98
|
+
const err = buildApiError(res.status, data);
|
|
99
|
+
err.retryAfter = retryAfterSeconds(res.headers.get('retry-after'));
|
|
100
|
+
return err;
|
|
101
|
+
}
|
|
76
102
|
/**
|
|
77
103
|
* Flatten the standard `{ error: { ... } }` envelope over the legacy top-level
|
|
78
104
|
* fields so structured capacity data is found in either shape. @internal
|
|
@@ -431,7 +457,7 @@ export class HttpClient {
|
|
|
431
457
|
}
|
|
432
458
|
const data = await res.json().catch(() => ({}));
|
|
433
459
|
if (!res.ok) {
|
|
434
|
-
throw
|
|
460
|
+
throw errorFromResponse(res, data);
|
|
435
461
|
}
|
|
436
462
|
return data;
|
|
437
463
|
}
|
|
@@ -486,7 +512,7 @@ export class HttpClient {
|
|
|
486
512
|
}
|
|
487
513
|
const data = await res.json().catch(() => ({}));
|
|
488
514
|
if (!res.ok) {
|
|
489
|
-
throw
|
|
515
|
+
throw errorFromResponse(res, data);
|
|
490
516
|
}
|
|
491
517
|
return data;
|
|
492
518
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { Loop } from './resources/loop.js';
|
|
|
36
36
|
* SDK version. Sent as part of the User-Agent header.
|
|
37
37
|
* Must match package.json "version" -- enforced by a contract test.
|
|
38
38
|
*/
|
|
39
|
-
export declare const VERSION = "0.2.22-dev.
|
|
39
|
+
export declare const VERSION = "0.2.22-dev.319";
|
|
40
40
|
export declare class RunBiOS {
|
|
41
41
|
/** Search models, fetch configs, check adapter compatibility. */
|
|
42
42
|
readonly models: Models;
|
|
@@ -74,8 +74,8 @@ export { Integrations, type HuggingFaceIntegration, type IntegrationBrowseParams
|
|
|
74
74
|
export { Training } from './resources/training.js';
|
|
75
75
|
export { Wallet } from './resources/wallet.js';
|
|
76
76
|
export { GPU, type GPURecommendation } from './resources/gpu.js';
|
|
77
|
-
export { Inference, validateChatRequest, parseSSE, buildInferenceRequest, contextSizingBasis, type ContextSizingBasis, CONTEXT_DEFAULT_CEILING, CONTEXT_EDITABLE_FLOOR, type ChatMessage, type FunctionTool, type ChatCompletionParams, type ChatCompletionResponse, type ChatCompletionChunk, type InferenceVerbParams, type CompletionParams, type EmbeddingParams, type RerankParams, type AnthropicMessageParams, type ServerlessUsageWindow, type ServerlessTimeseriesMetric, type ServerlessWorkspaceLimits, type ServerlessUsageEnvelope, type ServerlessUsageOverviewResponse, type ServerlessUsageRowsResponse, type ServerlessUsageTimeseriesResponse, type ServerlessUsageDailyResponse, type ServerlessSavingsTotals, type ServerlessSavingsPeriod, type ServerlessUsageSavingsResponse, } from './resources/inference.js';
|
|
78
|
-
export type { BiOSConfig, PaginatedResponse, ApiErrorBody, AvailableGpuAlternative, CapacityMinimumRequirement, InferenceBookingAccepted, Model, ModelSearchParams, ModelSearchResponse, ModelDetailResponse, InferenceModel, InferenceModelListResponse, ModelConfig, Dataset, DatasetListParams, DatasetListResponse, DatasetUploadParams, DatasetPreview, DatasetPreviewParams, DatasetImportHFParams, DatasetRegisterHFParams, DatasetHubSearchParams, DatasetHubPreviewParams, DatasetValidation, DatasetFormatVariant, DatasetFormatSpec, DatasetFormatSpecs, DatasetStorageUsage, TrainingMethod, RLHFAlgorithm, AdapterType, TrainingJobStatus, TrainingStatusFilter, TrainingCreateParams, TrainingStartFromParams, TrainingStartFrom, TrainingListParams, TrainingListResponse, TrainingJob, TrainingMetrics, TrainingResourceMetrics, TrainingDeviceMetrics, MetricPoint, MetricGraphConfig, TrainingCheckpoint, TrainingLogs, TrainingLogEntry, TrainingStopResponse, TrainingResumeResponse, CanonicalTrainingRequest, TrainingPreflightDataset, TrainingPreflightWarning, TrainingPreflightResponse, TrainingCapabilityChoice, TrainingConfigFieldCapability, TrainingCapabilities, TrainingFeature, GPUChoice, WalletBalance, Transaction, TransactionListResponse, TransactionListParams, GPUInfo, GPUPricingResponse, GPUOptionsParams, TrainingRecommendParams, TrainingAdvisorVerdict, TrainingAdvisorRecommendation, TrainingAdvisorUserShape, TrainingAdvisorJustification, TrainingAdvisorBasis, GPUOption, GPUOptionSuggestion, GPUOptionsResponse, InferenceStatus, InferenceCreateParams, InferenceUpdateParams, InferenceDeployment, InferenceDeploymentSummary, InferenceCreateResponse, InferenceListResponse, InferenceUpdateResponse, InferenceLifecycleResponse, InferenceDeleteResponse, InferenceGPUOptionMarket, InferenceGPUOption, InferenceGPUAlternative, InferenceGPUOptionsParams, InferenceGPUOptionsResponse, InferenceCanonicalRequest, InferencePreflightWarning, InferencePreflightResponse, AdapterCompatibility, AdapterCompatibilityResponse, AdapterCompatibilityParams, SupportedArchitecture, ArchitectureScope, SupportedArchitecturesResponse, ApiKey, ApiKeyScope, ApiKeyIntrospection, Organization, OrgMember, OrgInvite, Workspace, Integration, IntegrationCreateParams, StorageUsage, StorageObject, PromptTokensDetails, ChatCompletionUsage, ServingKind, TrainingRulePausedReason, TrainingTrigger, TrainingRecipe, TrainingRunState, TrainingVerdict, TrainingDecision, EvaluationStatus, EvaluationItemStatus, ItemErrorCode, EvaluationWinner, EvaluationItemWinnerFilter, ReportStatusFilter, ReportSort, EvaluationWarningCode, TrainingToolCall, TrainingMessage, TrainingRuleServing, TrainingRunPromoteRequest, TrainingRunRejectRequest, TrainingRunRollbackRequest, TrainingRunCancelRequest, TrainingRunListParams, TrainingRun, TrainingRunSummary, TrainingRunEvent, TrainingRunLinks, TrainingRunActions, EvaluationRef, EvaluationDecoding, EvaluationJudgeDimension, EvaluationDimensionScore, EvaluationWarning, EvaluationMargin, Evaluation, EvaluationItemJudge, EvaluationReferenceKind, EvaluationReference, EvaluationPairwiseOrder, EvaluationItemPairwise, EvaluationItemSide, EvaluationItem, EvaluationItemListParams, BenchmarkStatus, BenchmarkSourceKind, BenchmarkRunStatus, BenchmarkScoring, BenchmarkCheck, BenchmarkDimensionScore, Benchmark, BenchmarkItem, BenchmarkRun, BenchmarkRunItemSide, BenchmarkRunItem, BenchmarkRunItemListParams, BenchmarkHistoryPoint, BenchmarkSource, BenchmarkCreateParams, BenchmarkListParams, BenchmarkItemListParams, BenchmarkHistoryParams, BenchmarkRetireParams, InferenceAlias, InferenceAliasRequest, TrainingRuleDeleteResponse, TrainingRunListResponse, TrainingRunBenchmarkRun, TrainingRunResponse, TrainingRunActionResponse, EvaluationItemsResponse, BenchmarkRunItemsResponse, InferenceAliasListResponse, InferenceAliasResponse, InferenceAliasDeleteResponse, BenchmarkListResponse, BenchmarkItemsResponse, BenchmarkHistoryResponse, TrainingRuleRunRefusalCode, TrainingRuleSaveRefusalCode, TrainingRuleMonthlyLimitRefusal, PipelineStatus, PipelineActiveRun, PipelineVersion, PipelineAttempt, PipelineAttemptTrigger, Lineage, PipelineChain, PipelineChainRebuild, TrainingRunRollbackRefusalCode, LoopTraceRebuild, LoopTraceDeleteResponse, PipelineAttemptOutcome, PipelineLiveDeploymentStatus, PipelineLiveDeployment, LoopStorageWhere, LoopStorage, Pipeline, PipelineListParams, PipelineListResponse, PipelineResponse, PipelineTrainWhen, PipelineSchedule, PipelineWriteRequest, PipelineMutationResponse, PipelineImportParams, PipelineFloors, HeldBackFloors, LoopModel, LoopModelsResponse, LoopTracePipeline, LoopTracePipelinesResponse, LoopMetricsHistoryParams, LoopMetricPoint, LoopSignalDay, LoopMetricsHistory, PromotionPolicyState, RuleBenchmark, PipelineBenchmarksResponse, PipelineBenchmarkAttachParams, PipelineBenchmarkMoveParams, CompareBenchmark, VersionBenchmarkScore, PromotionMeasurement, PromotionOutcome, VersionHoldout, VersionScores, VersionScoreDifference, VersionCompareResponse, VersionCompareParams, TrainingRunPromotion, VerdictCheck, VerdictCheckKey, VerdictChecksWithheld, TrainingRunCost, AttemptDirection, AttemptHoldout, AttemptCompareSide, AttemptSharedSample, AttemptSharedFigures, AttemptBenchmarkSide, AttemptCompareBenchmark, AttemptCompareItemSide, AttemptCompareItem, AttemptCompareResponse, AttemptCompareParams, } from './types.js';
|
|
77
|
+
export { Inference, validateChatRequest, parseSSE, buildInferenceRequest, contextSizingBasis, type ContextSizingBasis, CONTEXT_DEFAULT_CEILING, CONTEXT_EDITABLE_FLOOR, type ChatMessage, type FunctionTool, type ChatCompletionParams, type ChatCompletionResponse, type ChatCompletionChunk, type InferenceVerbParams, type CompletionParams, type EmbeddingParams, type RerankParams, type AnthropicMessageParams, type AnthropicMessageResponse, type LoopCaptureOption, type LoopSampleIdOnResult, LOOP_PIPELINE_HEADER, LOOP_SAMPLE_ID_HEADER, type ServerlessUsageWindow, type ServerlessTimeseriesMetric, type ServerlessWorkspaceLimits, type ServerlessUsageEnvelope, type ServerlessUsageOverviewResponse, type ServerlessUsageRowsResponse, type ServerlessUsageTimeseriesResponse, type ServerlessUsageDailyResponse, type ServerlessSavingsTotals, type ServerlessSavingsPeriod, type ServerlessUsageSavingsResponse, } from './resources/inference.js';
|
|
78
|
+
export type { BiOSConfig, PaginatedResponse, ApiErrorBody, AvailableGpuAlternative, CapacityMinimumRequirement, InferenceBookingAccepted, Model, ModelSearchParams, ModelSearchResponse, ModelDetailResponse, InferenceModel, InferenceModelListResponse, ModelConfig, Dataset, DatasetListParams, DatasetListResponse, DatasetUploadParams, DatasetPreview, DatasetPreviewParams, DatasetImportHFParams, DatasetRegisterHFParams, DatasetHubSearchParams, DatasetHubPreviewParams, DatasetValidation, DatasetFormatVariant, DatasetFormatSpec, DatasetFormatSpecs, DatasetStorageUsage, TrainingMethod, RLHFAlgorithm, AdapterType, TrainingJobStatus, TrainingStatusFilter, TrainingCreateParams, TrainingStartFromParams, TrainingStartFrom, TrainingListParams, TrainingListResponse, TrainingJob, TrainingMetrics, TrainingResourceMetrics, TrainingDeviceMetrics, MetricPoint, MetricGraphConfig, TrainingCheckpoint, TrainingLogs, TrainingLogEntry, TrainingStopResponse, TrainingResumeResponse, CanonicalTrainingRequest, TrainingPreflightDataset, TrainingPreflightWarning, TrainingPreflightResponse, TrainingCapabilityChoice, TrainingConfigFieldCapability, TrainingCapabilities, TrainingFeature, GPUChoice, WalletBalance, Transaction, TransactionListResponse, TransactionListParams, GPUInfo, GPUPricingResponse, GPUOptionsParams, TrainingRecommendParams, TrainingAdvisorVerdict, TrainingAdvisorRecommendation, TrainingAdvisorUserShape, TrainingAdvisorJustification, TrainingAdvisorBasis, GPUOption, GPUOptionSuggestion, GPUOptionsResponse, InferenceStatus, InferenceCreateParams, InferenceUpdateParams, InferenceDeployment, InferenceDeploymentSummary, InferenceCreateResponse, InferenceListResponse, InferenceUpdateResponse, InferenceLifecycleResponse, InferenceDeleteResponse, InferenceGPUOptionMarket, InferenceGPUOption, InferenceGPUAlternative, InferenceGPUOptionsParams, InferenceGPUOptionsResponse, InferenceCanonicalRequest, InferencePreflightWarning, InferencePreflightResponse, AdapterCompatibility, AdapterCompatibilityResponse, AdapterCompatibilityParams, SupportedArchitecture, ArchitectureScope, SupportedArchitecturesResponse, ApiKey, ApiKeyScope, ApiKeyIntrospection, Organization, OrgMember, OrgInvite, Workspace, Integration, IntegrationCreateParams, StorageUsage, StorageObject, PromptTokensDetails, ChatCompletionUsage, ServingKind, TrainingRulePausedReason, TrainingTrigger, TrainingRecipe, TrainingRunState, TrainingVerdict, TrainingDecision, EvaluationStatus, EvaluationItemStatus, ItemErrorCode, EvaluationWinner, EvaluationItemWinnerFilter, ReportStatusFilter, ReportSort, EvaluationWarningCode, TrainingToolCall, TrainingMessage, TrainingRuleServing, TrainingRunPromoteRequest, TrainingRunRejectRequest, TrainingRunRollbackRequest, TrainingRunCancelRequest, TrainingRunListParams, TrainingRun, TrainingRunSummary, TrainingRunEvent, TrainingRunLinks, TrainingRunActions, EvaluationRef, EvaluationDecoding, EvaluationJudgeDimension, EvaluationDimensionScore, EvaluationWarning, EvaluationMargin, Evaluation, EvaluationItemJudge, EvaluationReferenceKind, EvaluationReference, EvaluationPairwiseOrder, EvaluationItemPairwise, EvaluationItemSide, EvaluationItem, EvaluationItemListParams, BenchmarkStatus, BenchmarkSourceKind, BenchmarkRunStatus, BenchmarkScoring, BenchmarkCheck, BenchmarkDimensionScore, Benchmark, BenchmarkItem, BenchmarkRun, BenchmarkRunItemSide, BenchmarkRunItem, BenchmarkRunItemListParams, BenchmarkHistoryPoint, BenchmarkSource, BenchmarkCreateParams, BenchmarkListParams, BenchmarkItemListParams, BenchmarkHistoryParams, BenchmarkRetireParams, InferenceAlias, InferenceAliasRequest, TrainingRuleDeleteResponse, TrainingRunListResponse, TrainingRunBenchmarkRun, TrainingRunResponse, TrainingRunActionResponse, EvaluationItemsResponse, BenchmarkRunItemsResponse, InferenceAliasListResponse, InferenceAliasResponse, InferenceAliasDeleteResponse, BenchmarkListResponse, BenchmarkItemsResponse, BenchmarkHistoryResponse, TrainingRuleRunRefusalCode, TrainingRuleSaveRefusalCode, TrainingRuleMonthlyLimitRefusal, PipelineStatus, PipelineActiveRun, PipelineVersion, PipelineAttempt, AttemptExplanation, PipelineAttemptListParams, PipelineAttemptListResponse, PipelineAttemptModelFilter, PipelineAttemptResultFilter, PipelineAttemptTrigger, Lineage, PipelineChain, PipelineChainRebuild, TrainingRunRollbackRefusalCode, LoopTraceRebuild, LoopTraceDeleteResponse, PipelineAttemptOutcome, PipelineLiveDeploymentStatus, PipelineLiveDeployment, LoopStorageWhere, LoopStorage, Pipeline, PipelineListParams, PipelineListStatus, PipelineListResponse, PipelineTrainingSettings, PipelineTrainingDefaults, PipelineTrainingRequest, PipelineResponse, PipelineTrainWhen, PipelineSchedule, PipelineWriteRequest, PipelineMutationResponse, PipelineImportParams, PipelineFloors, HeldBackFloors, LoopModel, LoopModelsResponse, LoopUnavailableModel, LoopUnavailableReasonCode, LoopTracePipeline, LoopTracePipelinesResponse, LoopMetricsHistoryParams, LoopMetricPoint, LoopSignalDay, LoopMetricsHistory, PromotionPolicyState, RuleBenchmark, PipelineBenchmarksResponse, PipelineBenchmarkAttachParams, PipelineBenchmarkMoveParams, CompareBenchmark, VersionBenchmarkScore, PromotionMeasurement, PromotionOutcome, VersionHoldout, VersionScores, VersionScoreDifference, VersionCompareResponse, VersionCompareParams, TrainingRunPromotion, VerdictCheck, VerdictCheckKey, VerdictChecksWithheld, TrainingRunCost, AttemptDirection, AttemptHoldout, AttemptCompareSide, AttemptSharedSample, AttemptSharedFigures, AttemptBenchmarkSide, AttemptCompareBenchmark, AttemptCompareItemSide, AttemptCompareItem, AttemptCompareResponse, AttemptCompareParams, } from './types.js';
|
|
79
79
|
/** The closed set of run states a training run never leaves. */
|
|
80
80
|
export { TERMINAL_RUN_STATES } from './types.js';
|
|
81
81
|
/** The most versions a pipeline may be set to make. */
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import { Loop } from './resources/loop.js';
|
|
|
36
36
|
* SDK version. Sent as part of the User-Agent header.
|
|
37
37
|
* Must match package.json "version" -- enforced by a contract test.
|
|
38
38
|
*/
|
|
39
|
-
export const VERSION = '0.2.22-dev.
|
|
39
|
+
export const VERSION = '0.2.22-dev.319';
|
|
40
40
|
export class RunBiOS {
|
|
41
41
|
/** Search models, fetch configs, check adapter compatibility. */
|
|
42
42
|
models;
|
|
@@ -102,7 +102,7 @@ export { Integrations } from './resources/integrations.js';
|
|
|
102
102
|
export { Training } from './resources/training.js';
|
|
103
103
|
export { Wallet } from './resources/wallet.js';
|
|
104
104
|
export { GPU } from './resources/gpu.js';
|
|
105
|
-
export { Inference, validateChatRequest, parseSSE, buildInferenceRequest, contextSizingBasis, CONTEXT_DEFAULT_CEILING, CONTEXT_EDITABLE_FLOOR, } from './resources/inference.js';
|
|
105
|
+
export { Inference, validateChatRequest, parseSSE, buildInferenceRequest, contextSizingBasis, CONTEXT_DEFAULT_CEILING, CONTEXT_EDITABLE_FLOOR, LOOP_PIPELINE_HEADER, LOOP_SAMPLE_ID_HEADER, } from './resources/inference.js';
|
|
106
106
|
/** The closed set of run states a training run never leaves. */
|
|
107
107
|
export { TERMINAL_RUN_STATES } from './types.js';
|
|
108
108
|
/** The most versions a pipeline may be set to make. */
|
|
@@ -49,6 +49,50 @@ export interface FunctionTool extends Record<string, unknown> {
|
|
|
49
49
|
strict?: boolean;
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Record one exchange into Conscious Loop pipelines with one header (the
|
|
54
|
+
* `loop` option of `chatCompletions`, `streamChatCompletions`, `messages` and
|
|
55
|
+
* `streamMessages`).
|
|
56
|
+
*
|
|
57
|
+
* The call goes to your model exactly as it would without it, with
|
|
58
|
+
* `X-Loop-Pipeline: <tag>[, <tag>...]` added: the gateway records the
|
|
59
|
+
* exchange (what was asked, what the model answered, its tool calls) as a
|
|
60
|
+
* sample of the pipelines those tags feed -- a pipeline's `own_tag` -- even
|
|
61
|
+
* where recording is off for that model, because the header is your consent
|
|
62
|
+
* for this one exchange. It answers with the sample's id in
|
|
63
|
+
* `X-Loop-Sample-Id`, before the body when streaming. Send feedback on that id
|
|
64
|
+
* with `client.loop.upvote`, `downvote` or `correct`: the sample id is the
|
|
65
|
+
* `traceId` they take. Feedback sent before the sample is recorded can be
|
|
66
|
+
* refused 409 `SAMPLE_NOT_YET_RECORDED`; send it again after the
|
|
67
|
+
* `Retry-After` seconds.
|
|
68
|
+
*
|
|
69
|
+
* Works for serverless models and dedicated deployments, on
|
|
70
|
+
* `/v1/chat/completions` and `/v1/messages` only.
|
|
71
|
+
*/
|
|
72
|
+
export interface LoopCaptureOption {
|
|
73
|
+
/** One pipeline tag, or several; each a non-empty tag with no comma. */
|
|
74
|
+
pipeline: string | string[];
|
|
75
|
+
/**
|
|
76
|
+
* Called once with the sample id as soon as the response headers arrive
|
|
77
|
+
* (before the first streamed chunk), or with null when the gateway recorded
|
|
78
|
+
* nothing for this exchange (no `X-Loop-Sample-Id` came back). A
|
|
79
|
+
* non-streaming result also carries it as `loop_sample_id`.
|
|
80
|
+
*/
|
|
81
|
+
onSampleId?: (sampleId: string | null) => void;
|
|
82
|
+
}
|
|
83
|
+
/** The request header the `loop` option sends. */
|
|
84
|
+
export declare const LOOP_PIPELINE_HEADER = "X-Loop-Pipeline";
|
|
85
|
+
/** The response header the gateway answers a recorded exchange with: the sample's id. */
|
|
86
|
+
export declare const LOOP_SAMPLE_ID_HEADER = "X-Loop-Sample-Id";
|
|
87
|
+
/**
|
|
88
|
+
* A non-streaming `/v1` answer, as the endpoint sent it, plus
|
|
89
|
+
* `loop_sample_id`: the `X-Loop-Sample-Id` the gateway answered with, or null
|
|
90
|
+
* when it sent none. It is NON-ENUMERABLE, so it is never part of the
|
|
91
|
+
* object's JSON or its keys; read it by name.
|
|
92
|
+
*/
|
|
93
|
+
export interface LoopSampleIdOnResult {
|
|
94
|
+
readonly loop_sample_id?: string | null;
|
|
95
|
+
}
|
|
52
96
|
export interface ChatCompletionParams extends Record<string, unknown> {
|
|
53
97
|
messages: ChatMessage[];
|
|
54
98
|
model?: string;
|
|
@@ -75,8 +119,10 @@ export interface ChatCompletionParams extends Record<string, unknown> {
|
|
|
75
119
|
idempotencyKey?: string;
|
|
76
120
|
requestId?: string;
|
|
77
121
|
signal?: AbortSignal;
|
|
122
|
+
/** Record this exchange into Conscious Loop pipelines. See {@link LoopCaptureOption}. */
|
|
123
|
+
loop?: LoopCaptureOption;
|
|
78
124
|
}
|
|
79
|
-
export type ChatCompletionResponse = Record<string, unknown
|
|
125
|
+
export type ChatCompletionResponse = Record<string, unknown> & LoopSampleIdOnResult;
|
|
80
126
|
export type ChatCompletionChunk = Record<string, unknown>;
|
|
81
127
|
export interface InferenceVerbParams extends Record<string, unknown> {
|
|
82
128
|
model?: string;
|
|
@@ -99,7 +145,11 @@ export interface AnthropicMessageParams extends InferenceVerbParams {
|
|
|
99
145
|
messages: Array<Record<string, unknown>>;
|
|
100
146
|
max_tokens: number;
|
|
101
147
|
anthropicVersion?: string;
|
|
148
|
+
/** Record this exchange into Conscious Loop pipelines. See {@link LoopCaptureOption}. */
|
|
149
|
+
loop?: LoopCaptureOption;
|
|
102
150
|
}
|
|
151
|
+
/** A non-streaming `/v1/messages` answer, with `loop_sample_id` (see {@link LoopSampleIdOnResult}). */
|
|
152
|
+
export type AnthropicMessageResponse = Record<string, unknown> & LoopSampleIdOnResult;
|
|
103
153
|
export type ServerlessUsageWindow = '1h' | '24h' | '7d' | '30d' | '90d';
|
|
104
154
|
export type ServerlessTimeseriesMetric = 'requests' | 'tokens' | 'spend' | 'ttft_p50' | 'ttft_p95' | 'tps';
|
|
105
155
|
export interface ServerlessWorkspaceLimits {
|
|
@@ -359,12 +409,28 @@ export declare class Inference {
|
|
|
359
409
|
completions(params: CompletionParams): Promise<Record<string, unknown>>;
|
|
360
410
|
embeddings(params: EmbeddingParams): Promise<Record<string, unknown>>;
|
|
361
411
|
rerank(params: RerankParams): Promise<Record<string, unknown>>;
|
|
362
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Anthropic Messages (`/v1/messages`). With `loop: { pipeline }` the
|
|
414
|
+
* exchange is recorded into those pipelines and the result carries
|
|
415
|
+
* `loop_sample_id` (see {@link LoopCaptureOption}).
|
|
416
|
+
*/
|
|
417
|
+
messages(params: AnthropicMessageParams): Promise<AnthropicMessageResponse>;
|
|
363
418
|
streamCompletions(params: CompletionParams): AsyncGenerator<Record<string, unknown>>;
|
|
419
|
+
/** Anthropic Messages, streamed. With `loop`, `loop.onSampleId` hears the sample id before the first event. */
|
|
364
420
|
streamMessages(params: AnthropicMessageParams): AsyncGenerator<Record<string, unknown>>;
|
|
365
421
|
listModels(inferenceKey?: string): Promise<InferenceModelListResponse>;
|
|
366
422
|
retrieveModel(modelId: string, inferenceKey?: string): Promise<InferenceModel>;
|
|
423
|
+
/**
|
|
424
|
+
* OpenAI chat completions (`/v1/chat/completions`). With
|
|
425
|
+
* `loop: { pipeline: 'support-replies' }` the exchange is recorded into
|
|
426
|
+
* that pipeline and the result carries `loop_sample_id`, the id to send
|
|
427
|
+
* feedback on (see {@link LoopCaptureOption}).
|
|
428
|
+
*/
|
|
367
429
|
chatCompletions(params: ChatCompletionParams): Promise<ChatCompletionResponse>;
|
|
430
|
+
/**
|
|
431
|
+
* OpenAI chat completions, streamed. With `loop`, `loop.onSampleId` hears
|
|
432
|
+
* the sample id as soon as the headers arrive, before the first chunk.
|
|
433
|
+
*/
|
|
368
434
|
streamChatCompletions(params: ChatCompletionParams): AsyncGenerator<ChatCompletionChunk>;
|
|
369
435
|
}
|
|
370
436
|
export { buildInferenceRequest };
|
|
@@ -135,6 +135,43 @@ function buildInferenceRequest(params) {
|
|
|
135
135
|
body.max_price_hour_cents = params.maxPriceHourCents;
|
|
136
136
|
return body;
|
|
137
137
|
}
|
|
138
|
+
/** The request header the `loop` option sends. */
|
|
139
|
+
export const LOOP_PIPELINE_HEADER = 'X-Loop-Pipeline';
|
|
140
|
+
/** The response header the gateway answers a recorded exchange with: the sample's id. */
|
|
141
|
+
export const LOOP_SAMPLE_ID_HEADER = 'X-Loop-Sample-Id';
|
|
142
|
+
/** `X-Loop-Pipeline`'s value for a `loop` option, or a thrown Error before any request. */
|
|
143
|
+
function loopPipelineHeader(loop) {
|
|
144
|
+
if (!loop || typeof loop !== 'object')
|
|
145
|
+
throw new Error('loop must be an object: { pipeline }');
|
|
146
|
+
const tags = Array.isArray(loop.pipeline) ? loop.pipeline : [loop.pipeline];
|
|
147
|
+
if (tags.length === 0)
|
|
148
|
+
throw new Error('loop.pipeline must name at least one pipeline tag');
|
|
149
|
+
return tags.map((tag) => {
|
|
150
|
+
const value = typeof tag === 'string' ? tag.trim() : '';
|
|
151
|
+
// A comma would split one tag into two, and a control character would end
|
|
152
|
+
// the header: both are refused here rather than sent.
|
|
153
|
+
if (!value || /[,\u0000-\u001f\u007f]/.test(value)) {
|
|
154
|
+
throw new Error(`loop.pipeline: ${JSON.stringify(tag)} is not a pipeline tag`);
|
|
155
|
+
}
|
|
156
|
+
return value;
|
|
157
|
+
}).join(', ');
|
|
158
|
+
}
|
|
159
|
+
/** The sample id a response carries (null when none), told to the caller's onSampleId once. */
|
|
160
|
+
function takeLoopSampleId(response, loop) {
|
|
161
|
+
const id = response.headers.get(LOOP_SAMPLE_ID_HEADER)?.trim() || null;
|
|
162
|
+
if (loop?.onSampleId)
|
|
163
|
+
loop.onSampleId(id);
|
|
164
|
+
return id;
|
|
165
|
+
}
|
|
166
|
+
/** `loop_sample_id` on a non-streaming result, non-enumerable. */
|
|
167
|
+
function withLoopSampleId(payload, id) {
|
|
168
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
169
|
+
Object.defineProperty(payload, 'loop_sample_id', {
|
|
170
|
+
value: id, enumerable: false, writable: false, configurable: true,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return payload;
|
|
174
|
+
}
|
|
138
175
|
function serverlessRead(value, valid) {
|
|
139
176
|
const body = value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
140
177
|
if (!body || !valid(body)) {
|
|
@@ -792,7 +829,7 @@ export class Inference {
|
|
|
792
829
|
// OpenAI-compatible key-scoped inference — `/v1/chat/completions`
|
|
793
830
|
// --------------------------------------------------------------------------
|
|
794
831
|
prepare(params, stream) {
|
|
795
|
-
const { messages, model, tools, toolChoice, reasoningEffort, inferenceKey, idempotencyKey, requestId, signal, ...extra } = params;
|
|
832
|
+
const { messages, model, tools, toolChoice, reasoningEffort, inferenceKey, idempotencyKey, requestId, signal, loop, ...extra } = params;
|
|
796
833
|
const key = inferenceKey || this.key;
|
|
797
834
|
if (!key)
|
|
798
835
|
throw new Error('an inferenceKey is required');
|
|
@@ -814,6 +851,8 @@ export class Inference {
|
|
|
814
851
|
};
|
|
815
852
|
if (idempotencyKey)
|
|
816
853
|
headers['Idempotency-Key'] = idempotencyKey;
|
|
854
|
+
if (loop !== undefined)
|
|
855
|
+
headers[LOOP_PIPELINE_HEADER] = loopPipelineHeader(loop);
|
|
817
856
|
return { body, key, headers, signal };
|
|
818
857
|
}
|
|
819
858
|
abortContext(signal) {
|
|
@@ -831,7 +870,10 @@ export class Inference {
|
|
|
831
870
|
};
|
|
832
871
|
}
|
|
833
872
|
prepareVerb(params, stream, anthropic) {
|
|
834
|
-
const { inferenceKey, idempotencyKey, requestId, signal, anthropicVersion, stream: requestedStream, ...payload } = params;
|
|
873
|
+
const { inferenceKey, idempotencyKey, requestId, signal, anthropicVersion, stream: requestedStream, loop, ...payload } = params;
|
|
874
|
+
if (loop !== undefined && !anthropic) {
|
|
875
|
+
throw new Error('loop capture is offered on chatCompletions and messages only');
|
|
876
|
+
}
|
|
835
877
|
if (requestedStream !== undefined)
|
|
836
878
|
throw new Error('Choose the streaming method instead of setting stream in a request body');
|
|
837
879
|
const key = inferenceKey || this.key;
|
|
@@ -851,6 +893,8 @@ export class Inference {
|
|
|
851
893
|
}
|
|
852
894
|
headers['Anthropic-Version'] = anthropicVersion || '2023-06-01';
|
|
853
895
|
}
|
|
896
|
+
if (loop !== undefined)
|
|
897
|
+
headers[LOOP_PIPELINE_HEADER] = loopPipelineHeader(loop);
|
|
854
898
|
return { body: stream ? { ...payload, stream: true } : payload, headers, signal };
|
|
855
899
|
}
|
|
856
900
|
async sendVerb(path, params, anthropic = false) {
|
|
@@ -862,6 +906,8 @@ export class Inference {
|
|
|
862
906
|
});
|
|
863
907
|
if (!response.ok)
|
|
864
908
|
throw await apiError(response);
|
|
909
|
+
const loop = anthropic ? params.loop : undefined;
|
|
910
|
+
const sampleId = anthropic ? takeLoopSampleId(response, loop) : null;
|
|
865
911
|
let payload;
|
|
866
912
|
try {
|
|
867
913
|
payload = await response.json();
|
|
@@ -872,7 +918,7 @@ export class Inference {
|
|
|
872
918
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
873
919
|
throw new ApiError(response.status, { error: 'Inference endpoint returned no response object' });
|
|
874
920
|
}
|
|
875
|
-
return payload;
|
|
921
|
+
return anthropic ? withLoopSampleId(payload, sampleId) : payload;
|
|
876
922
|
}
|
|
877
923
|
catch (error) {
|
|
878
924
|
if (abort.controller.signal.aborted && !(error instanceof ApiError)) {
|
|
@@ -929,6 +975,8 @@ export class Inference {
|
|
|
929
975
|
if (!contentType.includes('text/event-stream')) {
|
|
930
976
|
throw new ApiError(response.status, { error: `Expected text/event-stream, received ${contentType || 'no content type'}` });
|
|
931
977
|
}
|
|
978
|
+
if (anthropic)
|
|
979
|
+
takeLoopSampleId(response, params.loop);
|
|
932
980
|
if (!response.body)
|
|
933
981
|
throw new ApiError(0, { error: 'Inference stream returned no body' });
|
|
934
982
|
for await (const data of parseSSE(response.body)) {
|
|
@@ -976,12 +1024,18 @@ export class Inference {
|
|
|
976
1024
|
rerank(params) {
|
|
977
1025
|
return this.sendVerb('/v1/rerank', params);
|
|
978
1026
|
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Anthropic Messages (`/v1/messages`). With `loop: { pipeline }` the
|
|
1029
|
+
* exchange is recorded into those pipelines and the result carries
|
|
1030
|
+
* `loop_sample_id` (see {@link LoopCaptureOption}).
|
|
1031
|
+
*/
|
|
979
1032
|
messages(params) {
|
|
980
1033
|
return this.sendVerb('/v1/messages', params, true);
|
|
981
1034
|
}
|
|
982
1035
|
async *streamCompletions(params) {
|
|
983
1036
|
yield* this.streamVerb('/v1/completions', params);
|
|
984
1037
|
}
|
|
1038
|
+
/** Anthropic Messages, streamed. With `loop`, `loop.onSampleId` hears the sample id before the first event. */
|
|
985
1039
|
async *streamMessages(params) {
|
|
986
1040
|
yield* this.streamVerb('/v1/messages', params, true);
|
|
987
1041
|
}
|
|
@@ -994,6 +1048,12 @@ export class Inference {
|
|
|
994
1048
|
throw new Error('RunBiOS: modelId is required to retrieve an inference model');
|
|
995
1049
|
return asInferenceModel(await this.modelRead(`/v1/models/${encodeURIComponent(id)}`, inferenceKey));
|
|
996
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* OpenAI chat completions (`/v1/chat/completions`). With
|
|
1053
|
+
* `loop: { pipeline: 'support-replies' }` the exchange is recorded into
|
|
1054
|
+
* that pipeline and the result carries `loop_sample_id`, the id to send
|
|
1055
|
+
* feedback on (see {@link LoopCaptureOption}).
|
|
1056
|
+
*/
|
|
997
1057
|
async chatCompletions(params) {
|
|
998
1058
|
const prepared = this.prepare(params, false);
|
|
999
1059
|
const abort = this.abortContext(prepared.signal);
|
|
@@ -1003,7 +1063,8 @@ export class Inference {
|
|
|
1003
1063
|
});
|
|
1004
1064
|
if (!response.ok)
|
|
1005
1065
|
throw await apiError(response);
|
|
1006
|
-
|
|
1066
|
+
const sampleId = takeLoopSampleId(response, params.loop);
|
|
1067
|
+
return withLoopSampleId(await response.json(), sampleId);
|
|
1007
1068
|
}
|
|
1008
1069
|
catch (error) {
|
|
1009
1070
|
if (abort.controller.signal.aborted && !(error instanceof ApiError)) {
|
|
@@ -1016,6 +1077,10 @@ export class Inference {
|
|
|
1016
1077
|
abort.remove();
|
|
1017
1078
|
}
|
|
1018
1079
|
}
|
|
1080
|
+
/**
|
|
1081
|
+
* OpenAI chat completions, streamed. With `loop`, `loop.onSampleId` hears
|
|
1082
|
+
* the sample id as soon as the headers arrive, before the first chunk.
|
|
1083
|
+
*/
|
|
1019
1084
|
async *streamChatCompletions(params) {
|
|
1020
1085
|
const prepared = this.prepare(params, true);
|
|
1021
1086
|
const abort = this.abortContext(prepared.signal);
|
|
@@ -1029,6 +1094,7 @@ export class Inference {
|
|
|
1029
1094
|
if (!contentType.includes('text/event-stream')) {
|
|
1030
1095
|
throw new ApiError(response.status, { error: `Expected text/event-stream, received ${contentType || 'no content type'}` });
|
|
1031
1096
|
}
|
|
1097
|
+
takeLoopSampleId(response, params.loop);
|
|
1032
1098
|
if (!response.body)
|
|
1033
1099
|
throw new ApiError(0, { error: 'Inference response has no body' });
|
|
1034
1100
|
for await (const data of parseSSE(response.body)) {
|
package/dist/resources/loop.d.ts
CHANGED
|
@@ -1,63 +1,11 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { LoopTrace, LoopTraceDeleteResponse, LoopTraceListResponse, LoopTraceListParams, LoopCaptureParams, LoopCaptureResult, LoopImportParams, LoopImportResult, LoopSignal, LoopSignalParams, LoopLabel, LoopLabelCount, LoopConfig, LoopConfigParams, LoopStats, LoopTracePipelinesResponse, LoopMetricsHistory, LoopMetricsHistoryParams, TrainingRuleDeleteResponse, TrainingRunListParams, TrainingRunListResponse, TrainingRunResponse, TrainingRunActionResponse, TrainingRunPromoteRequest, TrainingRunRejectRequest, TrainingRunRollbackRequest, TrainingRunCancelRequest, Evaluation, EvaluationItemListParams, EvaluationItemsResponse, Benchmark, BenchmarkCreateParams, BenchmarkHistoryParams, BenchmarkHistoryResponse, BenchmarkItemListParams, BenchmarkItemsResponse, BenchmarkListParams, BenchmarkListResponse, BenchmarkRetireParams, BenchmarkRun, BenchmarkRunItemListParams, BenchmarkRunItemsResponse, Pipeline, PipelineListParams, PipelineListResponse, PipelineWriteRequest, PipelineMutationResponse, LoopModelsResponse, PipelineImportParams, PipelineBenchmarksResponse, PipelineBenchmarkAttachParams, PipelineBenchmarkMoveParams, VersionCompareResponse, VersionCompareParams, AttemptCompareResponse, AttemptCompareParams } from '../types.js';
|
|
3
|
-
/**
|
|
4
|
-
* The Conscious Loop -- pipelines that keep improving a model from the
|
|
5
|
-
* conversations you send them.
|
|
6
|
-
*
|
|
7
|
-
* A pipeline is one use case: a model, the tags whose samples train it, when
|
|
8
|
-
* to train, and how a new version is promoted. Every time it trains it makes
|
|
9
|
-
* an attempt; an attempt that goes live becomes the next version: one that
|
|
10
|
-
* beats the live model, one a member approves, or, after a deleted
|
|
11
|
-
* conversation, a rebuild without it that is not clearly worse (a `manual`
|
|
12
|
-
* pipeline waits for a member even then).
|
|
13
|
-
*
|
|
14
|
-
* Nothing is captured until a source is switched on, and you can switch it
|
|
15
|
-
* off again at any time. Creating a pipeline switches on the source named by
|
|
16
|
-
* its own tag (`own_tag`), stamping that tag, and deleting it switches that
|
|
17
|
-
* source off again. Anything that looks like a credential or a personal detail
|
|
18
|
-
* is removed before the record is written, never afterwards.
|
|
19
|
-
*
|
|
20
|
-
* Requires an API key carrying `loop:read` and/or `loop:write`. Those scopes are
|
|
21
|
-
* deliberately absent from the Read Only preset, because what they return is
|
|
22
|
-
* your raw prompts and completions rather than catalog metadata.
|
|
23
|
-
*
|
|
24
|
-
* Offered on the development environment only for now. Anywhere else every
|
|
25
|
-
* method here, reads included, rejects with a `ComingSoonError` whose
|
|
26
|
-
* `code` is `LOOP_COMING_SOON` (HTTP 403): a deliberate product state, not an
|
|
27
|
-
* outage, so retrying cannot succeed there.
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```ts
|
|
31
|
-
* // 1. A pipeline: a model it can train, and its own tag.
|
|
32
|
-
* const { models } = await client.loop.listModels();
|
|
33
|
-
* const { pipeline } = await client.loop.createPipeline({
|
|
34
|
-
* name: 'Support replies',
|
|
35
|
-
* model: { id: models[0].id },
|
|
36
|
-
* });
|
|
37
|
-
*
|
|
38
|
-
* // 2. Send samples from your app to the pipeline's own source: its create
|
|
39
|
-
* // switched that source on, and it tags everything it records.
|
|
40
|
-
* const sent = await client.loop.capture({
|
|
41
|
-
* deployment_id: pipeline.own_tag!,
|
|
42
|
-
* model: 'gpt-4o',
|
|
43
|
-
* messages: [{ role: 'user', content: 'what is our refund window' }],
|
|
44
|
-
* completion: 'thirty days',
|
|
45
|
-
* });
|
|
46
|
-
*
|
|
47
|
-
* // 3. Say whether it was right, on the trace_id the capture answered with.
|
|
48
|
-
* // A correction is the most valuable answer.
|
|
49
|
-
* if (sent.trace_id) {
|
|
50
|
-
* await client.loop.correct(sent.trace_id, 'Thirty days from delivery, no questions asked.');
|
|
51
|
-
* }
|
|
52
|
-
*
|
|
53
|
-
* // 4. Once it has its minimum, it trains; versions appear on the pipeline.
|
|
54
|
-
* const p = await client.loop.getPipeline(pipeline.rule_id);
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
1
|
+
import { type HttpClient } from '../client.js';
|
|
2
|
+
import type { LoopTrace, LoopTraceDeleteResponse, LoopTraceListResponse, LoopTraceListParams, LoopCaptureParams, LoopCaptureResult, LoopImportParams, LoopImportResult, LoopSignal, LoopSignalParams, LoopLabel, LoopLabelCount, LoopConfig, LoopConfigParams, LoopStats, LoopTracePipelinesResponse, LoopMetricsHistory, LoopMetricsHistoryParams, TrainingRuleDeleteResponse, TrainingRunListParams, TrainingRunListResponse, TrainingRunResponse, TrainingRunActionResponse, TrainingRunPromoteRequest, TrainingRunRejectRequest, TrainingRunRollbackRequest, TrainingRunCancelRequest, Evaluation, EvaluationItemListParams, EvaluationItemsResponse, Benchmark, BenchmarkCreateParams, BenchmarkHistoryParams, BenchmarkHistoryResponse, BenchmarkItemListParams, BenchmarkItemsResponse, BenchmarkListParams, BenchmarkListResponse, BenchmarkRetireParams, BenchmarkRun, BenchmarkRunItemListParams, BenchmarkRunItemsResponse, Pipeline, PipelineAttemptListParams, PipelineAttemptListResponse, PipelineListParams, PipelineListResponse, PipelineWriteRequest, PipelineMutationResponse, LoopModelsResponse, PipelineImportParams, PipelineBenchmarksResponse, PipelineBenchmarkAttachParams, PipelineBenchmarkMoveParams, VersionCompareResponse, VersionCompareParams, AttemptCompareResponse, AttemptCompareParams } from '../types.js';
|
|
57
3
|
export declare class Loop {
|
|
58
4
|
private readonly _http;
|
|
59
5
|
/** @internal */
|
|
60
6
|
constructor(_http: HttpClient);
|
|
7
|
+
/** @internal How feedback waits between tries; tests replace it so nothing sleeps. */
|
|
8
|
+
_sleep: (ms: number) => Promise<void>;
|
|
61
9
|
/**
|
|
62
10
|
* Send one exchange to be captured.
|
|
63
11
|
*
|
|
@@ -139,7 +87,14 @@ export declare class Loop {
|
|
|
139
87
|
* a shorter list moves every row after the gap and imports it again.
|
|
140
88
|
*/
|
|
141
89
|
importRows(params: LoopImportParams): Promise<LoopImportResult>;
|
|
142
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* List captured conversations, newest first.
|
|
92
|
+
*
|
|
93
|
+
* `q` searches the text: 2 to 200 characters, any case, matched as written
|
|
94
|
+
* in every message, the answer and its tool calls. Samples kept in your own
|
|
95
|
+
* storage cannot be searched; a search answers how many were left out as
|
|
96
|
+
* `unsearched`.
|
|
97
|
+
*/
|
|
143
98
|
listTraces(params?: LoopTraceListParams): Promise<LoopTraceListResponse>;
|
|
144
99
|
/**
|
|
145
100
|
* Read one conversation, with every verdict and tag recorded on it.
|
|
@@ -176,6 +131,13 @@ export declare class Loop {
|
|
|
176
131
|
* `edited` or `gold` verdict is the example, in place of the model's answer;
|
|
177
132
|
* a `rejected` answer is left out. Nothing is learned from an answer that was
|
|
178
133
|
* rejected or replaced. `source` defaults to `human`.
|
|
134
|
+
*
|
|
135
|
+
* Feedback on an `X-Loop-Sample-Id` sent right after the answer can reach
|
|
136
|
+
* the service before the sample does, which answers 409
|
|
137
|
+
* `SAMPLE_NOT_YET_RECORDED` and stores nothing. This method, and so `upvote`,
|
|
138
|
+
* `downvote`, `correct` and `gold`, sends it again after the `Retry-After`
|
|
139
|
+
* seconds (2 when none is given), at most 6 times in all, and then throws
|
|
140
|
+
* that error. No other error is retried.
|
|
179
141
|
*/
|
|
180
142
|
signal(traceId: string, params: LoopSignalParams): Promise<LoopSignal>;
|
|
181
143
|
/**
|
|
@@ -356,15 +318,21 @@ export declare class Loop {
|
|
|
356
318
|
*/
|
|
357
319
|
cancelTrainingRun(id: string, params?: TrainingRunCancelRequest): Promise<TrainingRunActionResponse>;
|
|
358
320
|
/**
|
|
359
|
-
*
|
|
360
|
-
* the live model),
|
|
361
|
-
*
|
|
321
|
+
* The workspace's pipelines, a page at a time: its versions (the attempts
|
|
322
|
+
* that became the live model), its latest 5 attempts (`attempts_total` says
|
|
323
|
+
* how many; {@link listPipelineAttempts} pages through every one), which
|
|
324
|
+
* version is live, the attempt in flight, and what the pipeline is waiting
|
|
325
|
+
* for.
|
|
326
|
+
*
|
|
327
|
+
* `limit` is 100 when absent and at most 100; `q` matches the name, the tag
|
|
328
|
+
* or the model (case-insensitive); `status` keeps the pipelines `training`,
|
|
329
|
+
* waiting on you (`needs_you`), `serving` or `paused`.
|
|
362
330
|
*
|
|
363
331
|
* RETURNED WITH ITS ENVELOPE, because the list is not always all of them:
|
|
364
|
-
* the service returns
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
332
|
+
* the service returns one page, newest first, `total` is every pipeline
|
|
333
|
+
* that matches and `truncated` is true when more exist after this page. A
|
|
334
|
+
* caller that shows `pipelines` alone presents a short list as the whole of
|
|
335
|
+
* it; the next page is `{ offset: offset + pipelines.length }`.
|
|
368
336
|
*/
|
|
369
337
|
listPipelines(params?: PipelineListParams): Promise<PipelineListResponse>;
|
|
370
338
|
/**
|
|
@@ -378,6 +346,17 @@ export declare class Loop {
|
|
|
378
346
|
* at most, not an exact spend.
|
|
379
347
|
*/
|
|
380
348
|
getPipeline(id: string): Promise<Pipeline>;
|
|
349
|
+
/**
|
|
350
|
+
* A pipeline's attempts, newest first, a page at a time: every training run
|
|
351
|
+
* it made, winner or not, each the shape `attempts` has on the pipeline
|
|
352
|
+
* (with its `explanation` in plain words). `limit` is 50 when absent and at
|
|
353
|
+
* most 200. Narrow by `result` (`became_version`, `not_better`,
|
|
354
|
+
* `inconclusive`, `failed`, `stopped`, `running` -- which includes one
|
|
355
|
+
* waiting for a decision -- or `waiting_for_review`) and by `model` (`main`,
|
|
356
|
+
* or `second` for the second model's attempts); `total` follows the
|
|
357
|
+
* filters.
|
|
358
|
+
*/
|
|
359
|
+
listPipelineAttempts(id: string, params?: PipelineAttemptListParams): Promise<PipelineAttemptListResponse>;
|
|
381
360
|
/**
|
|
382
361
|
* The base models a pipeline can train: only those the engine trains with
|
|
383
362
|
* LoRA SFT and the platform can serve for the comparison, recommended first.
|
|
@@ -394,6 +373,9 @@ export declare class Loop {
|
|
|
394
373
|
* not learned yet plus a replay of what it already learned (about half
|
|
395
374
|
* of what it trains on, picked at random, so it is not forgotten), and a
|
|
396
375
|
* winner is merged in and served as one model.
|
|
376
|
+
* `unavailable` lists every other model the platform knows, each with
|
|
377
|
+
* why it is not offered here (`reason_code`, and `reason`, one plain
|
|
378
|
+
* sentence); `[]` from a service from before it was sent.
|
|
397
379
|
* When the list cannot be read it throws `ApiError` 503
|
|
398
380
|
* `MODELS_UNAVAILABLE`, whose `body.floors` still carries the floors.
|
|
399
381
|
*/
|
|
@@ -413,7 +395,9 @@ export declare class Loop {
|
|
|
413
395
|
* What is charged is the GPU time the runs actually use; the platform's own
|
|
414
396
|
* per-run and monthly limits stop a run that goes wrong. `holdout.percent`
|
|
415
397
|
* (5 to 20, default 5) is the share of its samples held back to judge each
|
|
416
|
-
* attempt and never trained on.
|
|
398
|
+
* attempt and never trained on. `training` sets any of its training
|
|
399
|
+
* settings (`epochs`, `learning_rate`, `lora_rank`, `lora_alpha`,
|
|
400
|
+
* `replay_percent`); left out, or null, every one is automatic.
|
|
417
401
|
*/
|
|
418
402
|
createPipeline(params: PipelineWriteRequest): Promise<PipelineMutationResponse>;
|
|
419
403
|
/**
|
|
@@ -421,7 +405,13 @@ export declare class Loop {
|
|
|
421
405
|
* the limit and `challenger_model: null` removes the challenger. `promotion`
|
|
422
406
|
* is how the promotion policy is set. A new `holdout.percent` is a new
|
|
423
407
|
* revision: lowering it keeps every sample already held back, and raising it
|
|
424
|
-
* holds back only new samples.
|
|
408
|
+
* holds back only new samples. `training` changes only the settings it
|
|
409
|
+
* carries -- `{ epochs: 8 }` sets the passes and keeps the rest,
|
|
410
|
+
* `{ epochs: null }` puts the passes back to automatic -- and
|
|
411
|
+
* `training: null` puts every one back to automatic; the next attempt
|
|
412
|
+
* trains with them, and earlier attempts keep the settings they trained
|
|
413
|
+
* with. Out of range is 422 `TRAINING_SETTING_OUT_OF_RANGE`, naming the
|
|
414
|
+
* field. Saving is the authorization, as on a create.
|
|
425
415
|
*/
|
|
426
416
|
updatePipeline(id: string, params: PipelineWriteRequest): Promise<PipelineMutationResponse>;
|
|
427
417
|
/**
|
|
@@ -585,6 +575,9 @@ export declare class Loop {
|
|
|
585
575
|
* series that silently dropped the runs where the benchmark could not be
|
|
586
576
|
* scored would read as an unbroken line and not be one, so those points come
|
|
587
577
|
* back with `candidate_score` null and `status_reason` saying why.
|
|
578
|
+
*
|
|
579
|
+
* `pipeline` (a pipeline's id) keeps only the points of that pipeline's
|
|
580
|
+
* attempts, so the trend is one pipeline's attempts by attempt number.
|
|
588
581
|
*/
|
|
589
582
|
getBenchmarkHistory(id: string, params?: BenchmarkHistoryParams): Promise<BenchmarkHistoryResponse>;
|
|
590
583
|
/**
|
package/dist/resources/loop.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ApiError } from '../client.js';
|
|
1
2
|
/**
|
|
2
3
|
* The Conscious Loop -- pipelines that keep improving a model from the
|
|
3
4
|
* conversations you send them.
|
|
@@ -52,12 +53,25 @@
|
|
|
52
53
|
* const p = await client.loop.getPipeline(pipeline.rule_id);
|
|
53
54
|
* ```
|
|
54
55
|
*/
|
|
56
|
+
/**
|
|
57
|
+
* The code feedback is refused with (409, `Retry-After`) when it names a sample
|
|
58
|
+
* id the gateway issued moments ago (`X-Loop-Sample-Id`) and the sample is not
|
|
59
|
+
* recorded yet. Nothing was stored, so sending the same feedback again is safe.
|
|
60
|
+
*/
|
|
61
|
+
const SAMPLE_NOT_YET_RECORDED = 'SAMPLE_NOT_YET_RECORDED';
|
|
62
|
+
/** Feedback is sent at most this many times while its sample is on its way. */
|
|
63
|
+
const FEEDBACK_TRIES = 6;
|
|
64
|
+
/** The wait when the 409 names none, and the longest wait honoured. */
|
|
65
|
+
const FEEDBACK_RETRY_DEFAULT_SECONDS = 2;
|
|
66
|
+
const FEEDBACK_RETRY_MAX_SECONDS = 30;
|
|
55
67
|
export class Loop {
|
|
56
68
|
_http;
|
|
57
69
|
/** @internal */
|
|
58
70
|
constructor(_http) {
|
|
59
71
|
this._http = _http;
|
|
60
72
|
}
|
|
73
|
+
/** @internal How feedback waits between tries; tests replace it so nothing sleeps. */
|
|
74
|
+
_sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
75
|
// ── capture ───────────────────────────────────────────────────────────
|
|
62
76
|
/**
|
|
63
77
|
* Send one exchange to be captured.
|
|
@@ -145,9 +159,18 @@ export class Loop {
|
|
|
145
159
|
return this._http.fetchPost('/api/loop/import', params);
|
|
146
160
|
}
|
|
147
161
|
// ── traces ────────────────────────────────────────────────────────────
|
|
148
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* List captured conversations, newest first.
|
|
164
|
+
*
|
|
165
|
+
* `q` searches the text: 2 to 200 characters, any case, matched as written
|
|
166
|
+
* in every message, the answer and its tool calls. Samples kept in your own
|
|
167
|
+
* storage cannot be searched; a search answers how many were left out as
|
|
168
|
+
* `unsearched`.
|
|
169
|
+
*/
|
|
149
170
|
async listTraces(params = {}) {
|
|
150
171
|
const q = new URLSearchParams();
|
|
172
|
+
if (params.q)
|
|
173
|
+
q.set('q', params.q);
|
|
151
174
|
if (params.deployment_id)
|
|
152
175
|
q.set('deployment_id', params.deployment_id);
|
|
153
176
|
if (params.conversation_id)
|
|
@@ -226,10 +249,29 @@ export class Loop {
|
|
|
226
249
|
* `edited` or `gold` verdict is the example, in place of the model's answer;
|
|
227
250
|
* a `rejected` answer is left out. Nothing is learned from an answer that was
|
|
228
251
|
* rejected or replaced. `source` defaults to `human`.
|
|
252
|
+
*
|
|
253
|
+
* Feedback on an `X-Loop-Sample-Id` sent right after the answer can reach
|
|
254
|
+
* the service before the sample does, which answers 409
|
|
255
|
+
* `SAMPLE_NOT_YET_RECORDED` and stores nothing. This method, and so `upvote`,
|
|
256
|
+
* `downvote`, `correct` and `gold`, sends it again after the `Retry-After`
|
|
257
|
+
* seconds (2 when none is given), at most 6 times in all, and then throws
|
|
258
|
+
* that error. No other error is retried.
|
|
229
259
|
*/
|
|
230
260
|
async signal(traceId, params) {
|
|
231
|
-
const
|
|
232
|
-
|
|
261
|
+
const path = `/api/loop/traces/${encodeURIComponent(traceId)}/signals`;
|
|
262
|
+
for (let tries = 1;; tries++) {
|
|
263
|
+
try {
|
|
264
|
+
const res = await this._http.fetchPost(path, params);
|
|
265
|
+
return res.signal;
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
const onItsWay = err instanceof ApiError && err.status === 409 && err.code === SAMPLE_NOT_YET_RECORDED;
|
|
269
|
+
if (!onItsWay || tries >= FEEDBACK_TRIES)
|
|
270
|
+
throw err;
|
|
271
|
+
const seconds = Math.min(err.retryAfter ?? FEEDBACK_RETRY_DEFAULT_SECONDS, FEEDBACK_RETRY_MAX_SECONDS);
|
|
272
|
+
await this._sleep(seconds * 1000);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
233
275
|
}
|
|
234
276
|
/**
|
|
235
277
|
* Thumbs up: this answer was good. Shorthand for `signal(id, { verdict:
|
|
@@ -481,20 +523,32 @@ export class Loop {
|
|
|
481
523
|
}
|
|
482
524
|
// ── pipelines ─────────────────────────────────────────────────────────
|
|
483
525
|
/**
|
|
484
|
-
*
|
|
485
|
-
* the live model),
|
|
486
|
-
*
|
|
526
|
+
* The workspace's pipelines, a page at a time: its versions (the attempts
|
|
527
|
+
* that became the live model), its latest 5 attempts (`attempts_total` says
|
|
528
|
+
* how many; {@link listPipelineAttempts} pages through every one), which
|
|
529
|
+
* version is live, the attempt in flight, and what the pipeline is waiting
|
|
530
|
+
* for.
|
|
531
|
+
*
|
|
532
|
+
* `limit` is 100 when absent and at most 100; `q` matches the name, the tag
|
|
533
|
+
* or the model (case-insensitive); `status` keeps the pipelines `training`,
|
|
534
|
+
* waiting on you (`needs_you`), `serving` or `paused`.
|
|
487
535
|
*
|
|
488
536
|
* RETURNED WITH ITS ENVELOPE, because the list is not always all of them:
|
|
489
|
-
* the service returns
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
537
|
+
* the service returns one page, newest first, `total` is every pipeline
|
|
538
|
+
* that matches and `truncated` is true when more exist after this page. A
|
|
539
|
+
* caller that shows `pipelines` alone presents a short list as the whole of
|
|
540
|
+
* it; the next page is `{ offset: offset + pipelines.length }`.
|
|
493
541
|
*/
|
|
494
542
|
async listPipelines(params = {}) {
|
|
495
543
|
const q = new URLSearchParams();
|
|
544
|
+
if (params.limit != null)
|
|
545
|
+
q.set('limit', String(params.limit));
|
|
496
546
|
if (params.offset)
|
|
497
547
|
q.set('offset', String(params.offset));
|
|
548
|
+
if (params.q)
|
|
549
|
+
q.set('q', params.q);
|
|
550
|
+
if (params.status)
|
|
551
|
+
q.set('status', params.status);
|
|
498
552
|
const qs = q.toString();
|
|
499
553
|
const res = await this._http.fetchGet(`/api/loop/pipelines${qs ? `?${qs}` : ''}`);
|
|
500
554
|
const pipelines = res.pipelines || [];
|
|
@@ -519,6 +573,31 @@ export class Loop {
|
|
|
519
573
|
const res = await this._http.fetchGet(`/api/loop/pipelines/${encodeURIComponent(id)}`);
|
|
520
574
|
return res.pipeline;
|
|
521
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* A pipeline's attempts, newest first, a page at a time: every training run
|
|
578
|
+
* it made, winner or not, each the shape `attempts` has on the pipeline
|
|
579
|
+
* (with its `explanation` in plain words). `limit` is 50 when absent and at
|
|
580
|
+
* most 200. Narrow by `result` (`became_version`, `not_better`,
|
|
581
|
+
* `inconclusive`, `failed`, `stopped`, `running` -- which includes one
|
|
582
|
+
* waiting for a decision -- or `waiting_for_review`) and by `model` (`main`,
|
|
583
|
+
* or `second` for the second model's attempts); `total` follows the
|
|
584
|
+
* filters.
|
|
585
|
+
*/
|
|
586
|
+
async listPipelineAttempts(id, params = {}) {
|
|
587
|
+
const q = new URLSearchParams();
|
|
588
|
+
if (params.limit != null)
|
|
589
|
+
q.set('limit', String(params.limit));
|
|
590
|
+
if (params.offset)
|
|
591
|
+
q.set('offset', String(params.offset));
|
|
592
|
+
if (params.result)
|
|
593
|
+
q.set('result', params.result);
|
|
594
|
+
if (params.model)
|
|
595
|
+
q.set('model', params.model);
|
|
596
|
+
const qs = q.toString();
|
|
597
|
+
const res = await this._http.fetchGet(`/api/loop/pipelines/${encodeURIComponent(id)}/attempts${qs ? `?${qs}` : ''}`);
|
|
598
|
+
const attempts = res.attempts || [];
|
|
599
|
+
return { attempts, total: typeof res.total === 'number' ? res.total : attempts.length };
|
|
600
|
+
}
|
|
522
601
|
// ── creating and changing pipelines ───────────────────────────────────
|
|
523
602
|
//
|
|
524
603
|
// One body -- a name, a model, tags, when to train, how to promote -- and
|
|
@@ -541,12 +620,15 @@ export class Loop {
|
|
|
541
620
|
* not learned yet plus a replay of what it already learned (about half
|
|
542
621
|
* of what it trains on, picked at random, so it is not forgotten), and a
|
|
543
622
|
* winner is merged in and served as one model.
|
|
623
|
+
* `unavailable` lists every other model the platform knows, each with
|
|
624
|
+
* why it is not offered here (`reason_code`, and `reason`, one plain
|
|
625
|
+
* sentence); `[]` from a service from before it was sent.
|
|
544
626
|
* When the list cannot be read it throws `ApiError` 503
|
|
545
627
|
* `MODELS_UNAVAILABLE`, whose `body.floors` still carries the floors.
|
|
546
628
|
*/
|
|
547
629
|
async listModels() {
|
|
548
630
|
const res = await this._http.fetchGet('/api/loop/models');
|
|
549
|
-
return { ...res, models: res.models || [] };
|
|
631
|
+
return { ...res, models: res.models || [], unavailable: res.unavailable || [] };
|
|
550
632
|
}
|
|
551
633
|
/**
|
|
552
634
|
* Create a pipeline -- one use case.
|
|
@@ -563,7 +645,9 @@ export class Loop {
|
|
|
563
645
|
* What is charged is the GPU time the runs actually use; the platform's own
|
|
564
646
|
* per-run and monthly limits stop a run that goes wrong. `holdout.percent`
|
|
565
647
|
* (5 to 20, default 5) is the share of its samples held back to judge each
|
|
566
|
-
* attempt and never trained on.
|
|
648
|
+
* attempt and never trained on. `training` sets any of its training
|
|
649
|
+
* settings (`epochs`, `learning_rate`, `lora_rank`, `lora_alpha`,
|
|
650
|
+
* `replay_percent`); left out, or null, every one is automatic.
|
|
567
651
|
*/
|
|
568
652
|
async createPipeline(params) {
|
|
569
653
|
return this._http.fetchPost('/api/loop/pipelines', params);
|
|
@@ -573,7 +657,13 @@ export class Loop {
|
|
|
573
657
|
* the limit and `challenger_model: null` removes the challenger. `promotion`
|
|
574
658
|
* is how the promotion policy is set. A new `holdout.percent` is a new
|
|
575
659
|
* revision: lowering it keeps every sample already held back, and raising it
|
|
576
|
-
* holds back only new samples.
|
|
660
|
+
* holds back only new samples. `training` changes only the settings it
|
|
661
|
+
* carries -- `{ epochs: 8 }` sets the passes and keeps the rest,
|
|
662
|
+
* `{ epochs: null }` puts the passes back to automatic -- and
|
|
663
|
+
* `training: null` puts every one back to automatic; the next attempt
|
|
664
|
+
* trains with them, and earlier attempts keep the settings they trained
|
|
665
|
+
* with. Out of range is 422 `TRAINING_SETTING_OUT_OF_RANGE`, naming the
|
|
666
|
+
* field. Saving is the authorization, as on a create.
|
|
577
667
|
*/
|
|
578
668
|
async updatePipeline(id, params) {
|
|
579
669
|
return this._http.fetchPut(`/api/loop/pipelines/${encodeURIComponent(id)}`, params);
|
|
@@ -811,9 +901,14 @@ export class Loop {
|
|
|
811
901
|
* series that silently dropped the runs where the benchmark could not be
|
|
812
902
|
* scored would read as an unbroken line and not be one, so those points come
|
|
813
903
|
* back with `candidate_score` null and `status_reason` saying why.
|
|
904
|
+
*
|
|
905
|
+
* `pipeline` (a pipeline's id) keeps only the points of that pipeline's
|
|
906
|
+
* attempts, so the trend is one pipeline's attempts by attempt number.
|
|
814
907
|
*/
|
|
815
908
|
async getBenchmarkHistory(id, params = {}) {
|
|
816
909
|
const q = new URLSearchParams();
|
|
910
|
+
if (params.pipeline)
|
|
911
|
+
q.set('pipeline', params.pipeline);
|
|
817
912
|
if (params.limit != null)
|
|
818
913
|
q.set('limit', String(params.limit));
|
|
819
914
|
if (params.offset != null)
|
package/dist/types.d.ts
CHANGED
|
@@ -2771,12 +2771,23 @@ export interface LoopTraceListParams {
|
|
|
2771
2771
|
signal?: 'good' | 'bad' | 'corrected';
|
|
2772
2772
|
/** Only the conversations nobody has given feedback on yet. */
|
|
2773
2773
|
unsignalled?: boolean;
|
|
2774
|
+
/**
|
|
2775
|
+
* Text search: 2 to 200 characters, case-insensitive, matched as written
|
|
2776
|
+
* (`%` and `_` are plain characters) in every message, the answer and the
|
|
2777
|
+
* answer's tool calls. Combines with every other filter.
|
|
2778
|
+
*/
|
|
2779
|
+
q?: string;
|
|
2774
2780
|
}
|
|
2775
2781
|
export interface LoopTraceListResponse {
|
|
2776
2782
|
traces: LoopTrace[];
|
|
2777
2783
|
total: number;
|
|
2778
2784
|
limit: number;
|
|
2779
2785
|
offset: number;
|
|
2786
|
+
/**
|
|
2787
|
+
* Present only when `q` was sent: how many of the samples the other filters
|
|
2788
|
+
* select keep their text in your own storage and so were not searched.
|
|
2789
|
+
*/
|
|
2790
|
+
unsearched?: number;
|
|
2780
2791
|
}
|
|
2781
2792
|
export interface LoopConfig {
|
|
2782
2793
|
deployment_id: string;
|
|
@@ -2995,8 +3006,9 @@ export type TrainingTrigger = 'rows' | 'cadence' | 'both' | 'manual' | 'variant'
|
|
|
2995
3006
|
* `served_as` is `adapter` for an attempt from the base and `merged` for one
|
|
2996
3007
|
* built on versions (one model with every version merged in). A winner is
|
|
2997
3008
|
* merged in and served as one model. `learned_deleted_conversation` is true
|
|
2998
|
-
* when a
|
|
2999
|
-
*
|
|
3009
|
+
* when it -- or a version it was built on, whose model is merged into it --
|
|
3010
|
+
* learned a conversation that has since been deleted: a rebuild without it is
|
|
3011
|
+
* on its way, and it can no longer be rolled back to.
|
|
3000
3012
|
*/
|
|
3001
3013
|
export interface Lineage {
|
|
3002
3014
|
trained_from: 'base' | 'versions';
|
|
@@ -3023,6 +3035,12 @@ export interface TrainingRecipe {
|
|
|
3023
3035
|
num_train_epochs: number | null;
|
|
3024
3036
|
lora_rank: number | null;
|
|
3025
3037
|
lora_alpha: number | null;
|
|
3038
|
+
/**
|
|
3039
|
+
* True when one of the pipeline's own training settings ({@link Pipeline.training})
|
|
3040
|
+
* was applied over the automatic recipe for this run, frozen when it started;
|
|
3041
|
+
* false when it trained on automatic settings throughout.
|
|
3042
|
+
*/
|
|
3043
|
+
custom: boolean;
|
|
3026
3044
|
}
|
|
3027
3045
|
export type TrainingRunState = 'built' | 'registering' | 'dataset_validating' | 'preflighting' | 'job_creating' | 'training' | 'checkpoint_ready' | 'candidate_booking' | 'candidate_running' | 'evaluating' | 'reported' | 'awaiting_review' | 'promoting' | 'waiting_funds'
|
|
3028
3046
|
/**
|
|
@@ -3926,6 +3944,8 @@ export interface BenchmarkItemListParams {
|
|
|
3926
3944
|
offset?: number;
|
|
3927
3945
|
}
|
|
3928
3946
|
export interface BenchmarkHistoryParams {
|
|
3947
|
+
/** Only the points of this pipeline's attempts (its id, `rule_id`). */
|
|
3948
|
+
pipeline?: string;
|
|
3929
3949
|
/** 50 when absent; at most 200. */
|
|
3930
3950
|
limit?: number;
|
|
3931
3951
|
offset?: number;
|
|
@@ -4012,6 +4032,8 @@ export type TrainingRuleRunRefusalCode = 'NOT_ENOUGH_SAMPLES' | 'PIPELINE_NEEDS_
|
|
|
4012
4032
|
* pipeline's current one (ignoring case) is kept, and whether it can still
|
|
4013
4033
|
* be trained is checked when an attempt starts.
|
|
4014
4034
|
* - `NO_GPU_FITS` (422): no machines can train or serve the model right now.
|
|
4035
|
+
* - `TRAINING_SETTING_OUT_OF_RANGE` (422): a `training` setting is outside its
|
|
4036
|
+
* range; the message names the field and the range. Nothing was saved.
|
|
4015
4037
|
* - `MODEL_REVISION_UNAVAILABLE` (422): the model could not be pinned to an
|
|
4016
4038
|
* exact revision because the training service did not answer. Try again
|
|
4017
4039
|
* once it does.
|
|
@@ -4038,7 +4060,7 @@ export type TrainingRuleRunRefusalCode = 'NOT_ENOUGH_SAMPLES' | 'PIPELINE_NEEDS_
|
|
|
4038
4060
|
* the key every comparison is scored with) did not answer just then. Try
|
|
4039
4061
|
* again in a minute.
|
|
4040
4062
|
*/
|
|
4041
|
-
export type TrainingRuleSaveRefusalCode = 'PIPELINE_NAME_TAKEN' | 'OWN_TAG_TAKEN' | 'REVISION_MISMATCH' | 'RUN_ACTIVE' | 'NOT_A_SIMPLE_PIPELINE' | 'MODEL_NOT_TRAINABLE' | 'NO_GPU_FITS' | 'MODEL_REVISION_UNAVAILABLE' | 'NO_JUDGE_MODEL' | 'PROMOTION_POLICY_UNKNOWN' | 'PROMOTION_K_REQUIRED' | 'PROMOTION_K_UNREACHABLE' | 'PROMOTION_POLICY_NEEDS_BENCHMARK' | 'TRAINING_NOT_ENABLED' | 'METHOD_NOT_ENABLED' | 'CEILING_TOO_LOW' | 'MODELS_UNAVAILABLE' | 'GPU_OPTIONS_UNAVAILABLE' | 'AGENT_COULD_NOT_START';
|
|
4063
|
+
export type TrainingRuleSaveRefusalCode = 'PIPELINE_NAME_TAKEN' | 'OWN_TAG_TAKEN' | 'REVISION_MISMATCH' | 'RUN_ACTIVE' | 'NOT_A_SIMPLE_PIPELINE' | 'MODEL_NOT_TRAINABLE' | 'NO_GPU_FITS' | 'TRAINING_SETTING_OUT_OF_RANGE' | 'MODEL_REVISION_UNAVAILABLE' | 'NO_JUDGE_MODEL' | 'PROMOTION_POLICY_UNKNOWN' | 'PROMOTION_K_REQUIRED' | 'PROMOTION_K_UNREACHABLE' | 'PROMOTION_POLICY_NEEDS_BENCHMARK' | 'TRAINING_NOT_ENABLED' | 'METHOD_NOT_ENABLED' | 'CEILING_TOO_LOW' | 'MODELS_UNAVAILABLE' | 'GPU_OPTIONS_UNAVAILABLE' | 'AGENT_COULD_NOT_START';
|
|
4042
4064
|
/**
|
|
4043
4065
|
* The body of a manual run refused `409 MONTHLY_LIMIT_REACHED`, as
|
|
4044
4066
|
* `ApiError.body` carries it.
|
|
@@ -4271,6 +4293,69 @@ export interface PipelineAttempt {
|
|
|
4271
4293
|
evaluation_id: string | null;
|
|
4272
4294
|
/** Its last sentence. */
|
|
4273
4295
|
reason: string | null;
|
|
4296
|
+
/**
|
|
4297
|
+
* What became of it in plain words, written by the service from its state,
|
|
4298
|
+
* verdict and error. See {@link AttemptExplanation}.
|
|
4299
|
+
*/
|
|
4300
|
+
explanation: AttemptExplanation | null;
|
|
4301
|
+
}
|
|
4302
|
+
/**
|
|
4303
|
+
* An attempt's outcome in plain words: `headline` ("Could not be tested"),
|
|
4304
|
+
* `detail` (what happened and why) and `next` (what happens now; null when
|
|
4305
|
+
* there is nothing to say).
|
|
4306
|
+
*/
|
|
4307
|
+
export interface AttemptExplanation {
|
|
4308
|
+
headline: string;
|
|
4309
|
+
detail: string;
|
|
4310
|
+
next: string | null;
|
|
4311
|
+
}
|
|
4312
|
+
/**
|
|
4313
|
+
* A pipeline's own training settings, each null while it is automatic (the
|
|
4314
|
+
* platform's value for its model, {@link PipelineTrainingDefaults}). A change
|
|
4315
|
+
* applies from the next attempt, and every attempt keeps the settings it
|
|
4316
|
+
* trained with on its `recipe` (`custom` true when one of these applied).
|
|
4317
|
+
* LoRA only: there is no full fine-tune, so the rank and alpha always apply.
|
|
4318
|
+
*/
|
|
4319
|
+
export interface PipelineTrainingSettings {
|
|
4320
|
+
/** Passes over the data, 1 to 15. */
|
|
4321
|
+
epochs: number | null;
|
|
4322
|
+
/** 1e-6 to 1e-2. */
|
|
4323
|
+
learning_rate: number | null;
|
|
4324
|
+
/** 4, 8, 16, 32 or 64. */
|
|
4325
|
+
lora_rank: number | null;
|
|
4326
|
+
/** 1 to 512. */
|
|
4327
|
+
lora_alpha: number | null;
|
|
4328
|
+
/**
|
|
4329
|
+
* 0 to 50: the share of already-learned samples an attempt replays when it
|
|
4330
|
+
* builds on the live version, so the new version does not forget them.
|
|
4331
|
+
*/
|
|
4332
|
+
replay_percent: number | null;
|
|
4333
|
+
}
|
|
4334
|
+
/**
|
|
4335
|
+
* What automatic means for this pipeline's model: what an attempt trains with
|
|
4336
|
+
* where {@link PipelineTrainingSettings} says null. The automatic recipe picks
|
|
4337
|
+
* its passes from `min_epochs` to `max_epochs` (at least 8 for Gemma 4).
|
|
4338
|
+
*/
|
|
4339
|
+
export interface PipelineTrainingDefaults {
|
|
4340
|
+
learning_rate: number;
|
|
4341
|
+
lora_rank: number;
|
|
4342
|
+
lora_alpha: number;
|
|
4343
|
+
replay_percent: number;
|
|
4344
|
+
min_epochs: number;
|
|
4345
|
+
max_epochs: number;
|
|
4346
|
+
}
|
|
4347
|
+
/**
|
|
4348
|
+
* The training settings a create or edit sends. Every key is three-state:
|
|
4349
|
+
* absent keeps the pipeline's, null puts it back to automatic, a number sets
|
|
4350
|
+
* it. Out of range is refused 422 `TRAINING_SETTING_OUT_OF_RANGE` naming the
|
|
4351
|
+
* field and its range.
|
|
4352
|
+
*/
|
|
4353
|
+
export interface PipelineTrainingRequest {
|
|
4354
|
+
epochs?: number | null;
|
|
4355
|
+
learning_rate?: number | null;
|
|
4356
|
+
lora_rank?: number | null;
|
|
4357
|
+
lora_alpha?: number | null;
|
|
4358
|
+
replay_percent?: number | null;
|
|
4274
4359
|
}
|
|
4275
4360
|
/** A model and the exact commit of it. */
|
|
4276
4361
|
export interface PipelineModelRef {
|
|
@@ -4425,6 +4510,10 @@ export interface Pipeline {
|
|
|
4425
4510
|
holdout: PipelineHoldout;
|
|
4426
4511
|
/** Rows the next version holds back and never trains on. Detail only; null on the list. */
|
|
4427
4512
|
holdout_size: number | null;
|
|
4513
|
+
/** Its training settings, each null while automatic. See {@link PipelineTrainingSettings}. */
|
|
4514
|
+
training: PipelineTrainingSettings;
|
|
4515
|
+
/** What automatic means for its model. See {@link PipelineTrainingDefaults}. */
|
|
4516
|
+
training_defaults: PipelineTrainingDefaults;
|
|
4428
4517
|
minimums: PipelineMinimums;
|
|
4429
4518
|
/**
|
|
4430
4519
|
* The pinned base every version is trained over: from it, or, where the
|
|
@@ -4499,9 +4588,17 @@ export interface Pipeline {
|
|
|
4499
4588
|
*/
|
|
4500
4589
|
month_spent_cents: number;
|
|
4501
4590
|
active_run: PipelineActiveRun | null;
|
|
4502
|
-
/**
|
|
4591
|
+
/** How many versions it has made: every one is in `versions` from `getPipeline`, only the latest few and the live one on `listPipelines`. */
|
|
4592
|
+
versions_total: number;
|
|
4593
|
+
/** How many attempts it has made; `attempts` holds only the latest of them. */
|
|
4594
|
+
attempts_total: number;
|
|
4595
|
+
/** Winners only, v1 first: every one from `getPipeline`, the latest few and the live one on `listPipelines`. */
|
|
4503
4596
|
versions: PipelineVersion[];
|
|
4504
|
-
/**
|
|
4597
|
+
/**
|
|
4598
|
+
* Its latest attempts, in attempt order: 20 from `getPipeline`, 5 on each
|
|
4599
|
+
* pipeline of `listPipelines`. `attempts_total` says how many there are,
|
|
4600
|
+
* and `listPipelineAttempts` pages through every one.
|
|
4601
|
+
*/
|
|
4505
4602
|
attempts: PipelineAttempt[];
|
|
4506
4603
|
}
|
|
4507
4604
|
export interface TrainingRuleDeleteResponse {
|
|
@@ -4658,12 +4755,23 @@ export interface BenchmarkHistoryResponse {
|
|
|
4658
4755
|
points: BenchmarkHistoryPoint[];
|
|
4659
4756
|
total: number;
|
|
4660
4757
|
}
|
|
4758
|
+
/**
|
|
4759
|
+
* The pipeline list's `status` filter: `training` (an attempt is running),
|
|
4760
|
+
* `needs_you` (waiting on a member: a review or funds), `serving` (its live
|
|
4761
|
+
* version answers) and `paused`.
|
|
4762
|
+
*/
|
|
4763
|
+
export type PipelineListStatus = 'training' | 'needs_you' | 'serving' | 'paused';
|
|
4661
4764
|
export interface PipelineListParams {
|
|
4765
|
+
/** 100 when absent; at most 100. */
|
|
4766
|
+
limit?: number;
|
|
4662
4767
|
/** How many of the newest pipelines to skip: the page after one that started at N starts at N + its length. */
|
|
4663
4768
|
offset?: number;
|
|
4769
|
+
/** A case-insensitive substring of the name, the tag or the model. */
|
|
4770
|
+
q?: string;
|
|
4771
|
+
status?: PipelineListStatus;
|
|
4664
4772
|
}
|
|
4665
4773
|
export interface PipelineListResponse {
|
|
4666
|
-
/** The newest pipelines first,
|
|
4774
|
+
/** The newest pipelines first, one page (100 unless `limit` says; at most 100). */
|
|
4667
4775
|
pipelines: Pipeline[];
|
|
4668
4776
|
/** Every pipeline in the workspace, including any not returned. */
|
|
4669
4777
|
total: number;
|
|
@@ -4673,6 +4781,22 @@ export interface PipelineListResponse {
|
|
|
4673
4781
|
export interface PipelineResponse {
|
|
4674
4782
|
pipeline: Pipeline;
|
|
4675
4783
|
}
|
|
4784
|
+
/** What became of an attempt, as `listPipelineAttempts` filters it. */
|
|
4785
|
+
export type PipelineAttemptResultFilter = 'became_version' | 'not_better' | 'inconclusive' | 'failed' | 'stopped' | 'running' | 'waiting_for_review';
|
|
4786
|
+
/** Which model's attempts: the pipeline's own (`main`) or its second model's (`second`). */
|
|
4787
|
+
export type PipelineAttemptModelFilter = 'main' | 'second';
|
|
4788
|
+
export interface PipelineAttemptListParams {
|
|
4789
|
+
/** 50 when absent; at most 200. */
|
|
4790
|
+
limit?: number;
|
|
4791
|
+
offset?: number;
|
|
4792
|
+
result?: PipelineAttemptResultFilter;
|
|
4793
|
+
model?: PipelineAttemptModelFilter;
|
|
4794
|
+
}
|
|
4795
|
+
/** One page of a pipeline's attempts, newest first. `total` follows the filters. */
|
|
4796
|
+
export interface PipelineAttemptListResponse {
|
|
4797
|
+
attempts: PipelineAttempt[];
|
|
4798
|
+
total: number;
|
|
4799
|
+
}
|
|
4676
4800
|
/**
|
|
4677
4801
|
* The body of `POST /api/loop/pipelines` and (every field optional)
|
|
4678
4802
|
* `PUT /api/loop/pipelines/{id}`. Everything else -- the machines, the money,
|
|
@@ -4729,6 +4853,12 @@ export interface PipelineWriteRequest {
|
|
|
4729
4853
|
/** 1 to 100, or null for no limit (the default). Counts versions, not attempts. */
|
|
4730
4854
|
max_versions?: number | null;
|
|
4731
4855
|
enabled?: boolean;
|
|
4856
|
+
/**
|
|
4857
|
+
* The pipeline's training settings. Absent: unchanged on an edit (automatic
|
|
4858
|
+
* on a create). null: every one back to automatic. An object: only the keys
|
|
4859
|
+
* it carries change, each null for automatic. See {@link PipelineTrainingRequest}.
|
|
4860
|
+
*/
|
|
4861
|
+
training?: PipelineTrainingRequest | null;
|
|
4732
4862
|
/** An edit's compare-and-set. */
|
|
4733
4863
|
expected_revision?: number;
|
|
4734
4864
|
}
|
|
@@ -4781,8 +4911,25 @@ export interface HeldBackFloors {
|
|
|
4781
4911
|
max_percent: number;
|
|
4782
4912
|
scored_rows: number;
|
|
4783
4913
|
}
|
|
4914
|
+
/** Why a model is not offered for a pipeline ({@link LoopUnavailableModel.reason_code}). */
|
|
4915
|
+
export type LoopUnavailableReasonCode = 'not_trainable_sft_lora' | 'not_servable_as_adapter' | 'too_large_for_available_gpus' | 'not_verified' | 'registry_missing';
|
|
4916
|
+
/**
|
|
4917
|
+
* A model the platform knows that a pipeline cannot use, and why. `reason` is
|
|
4918
|
+
* one plain sentence to show as it is; `reason_code` is one of
|
|
4919
|
+
* {@link LoopUnavailableReasonCode}, typed as a string so a code added later
|
|
4920
|
+
* still carries its sentence.
|
|
4921
|
+
*/
|
|
4922
|
+
export interface LoopUnavailableModel {
|
|
4923
|
+
id: string;
|
|
4924
|
+
name: string;
|
|
4925
|
+
family: string | null;
|
|
4926
|
+
reason_code: string;
|
|
4927
|
+
reason: string;
|
|
4928
|
+
}
|
|
4784
4929
|
export interface LoopModelsResponse {
|
|
4785
4930
|
models: LoopModel[];
|
|
4931
|
+
/** Every other model the platform knows, each with why it is not offered here. */
|
|
4932
|
+
unavailable: LoopUnavailableModel[];
|
|
4786
4933
|
floors: PipelineFloors;
|
|
4787
4934
|
}
|
|
4788
4935
|
/** The promotion rule as the API reads it back. */
|