@nemesis-oss/ollama-sdk 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -3443,6 +3443,151 @@ interface FormattedTelemetry {
3443
3443
  */
3444
3444
  declare function formatTelemetry(raw: RawOllamaMetrics): FormattedTelemetry;
3445
3445
 
3446
+ /**
3447
+ * Speculative-decoding draft benchmark — measures whether the runtime
3448
+ * `draft_num_predict` option provides a net throughput gain for a specific
3449
+ * prompt (October 13 digest; see ADR 0030).
3450
+ *
3451
+ * Upstream Ollama does not emit a dedicated draft-token acceptance counter —
3452
+ * speculative efficiency is only observable as effective throughput
3453
+ * (`eval_count` / `eval_duration`). And the signal is noisy: rejected draft
3454
+ * predictions *add* compute (high-temperature, creative, non-deterministic
3455
+ * prompts), while accepted drafts dramatically cut `eval_duration` on
3456
+ * predictable workloads (structured code generation, JSON extraction). A
3457
+ * single run therefore proves nothing — this helper implements the
3458
+ * deterministic profiling protocol the upstream observations call for:
3459
+ *
3460
+ * - **Deterministic sampling** — `temperature: 0` is forced and a fixed
3461
+ * `seed` (default 42) is stamped on every measured run, so both sides
3462
+ * generate comparable token streams.
3463
+ * - **Warm-up discard** — one throwaway `num_predict: 1` generation per
3464
+ * setting pays the model load (`load_duration`) before any sample is
3465
+ * taken.
3466
+ * - **Identical prompt, both sides** — baseline (`draft_num_predict: 0`,
3467
+ * drafting disabled) vs. speculative (`draft_num_predict: N`), several
3468
+ * samples each, **median** compared (single samples mislead).
3469
+ * - Throughput comes from `formatTelemetry` — the official Usage-doc
3470
+ * tokens/second math, never hand-rolled nanosecond division.
3471
+ *
3472
+ * `effectiveGain` is true when the reported `speedupMultiplier` exceeds
3473
+ * `gainThreshold` (default 1.05 — five percent, past run-to-run noise). A
3474
+ * speedup of about 1.0 usually means the model has **no draft network** (and
3475
+ * no embedded MTP tensors): the server silently ignores the option and both
3476
+ * sides measure the same — see `lab/15-speculative-draft` for a per-setting
3477
+ * table over 0/4/8/16.
3478
+ *
3479
+ * ```ts
3480
+ * import { OllamaClient, compareDraftPerformance } from '@nemesis-oss/ollama-sdk';
3481
+ *
3482
+ * const comparison = await compareDraftPerformance(new OllamaClient(), {
3483
+ * model: 'llama3.1',
3484
+ * prompt: 'Extract the sender, subject, and date from this email as JSON: ...',
3485
+ * });
3486
+ * if (comparison.effectiveGain) {
3487
+ * // drafting pays off for this prompt
3488
+ * }
3489
+ * ```
3490
+ */
3491
+
3492
+ /**
3493
+ * Structural dependency of {@link compareDraftPerformance}: anything that can
3494
+ * run a non-streaming `/api/generate` call. `OllamaClient` satisfies it
3495
+ * without being named (same discipline as the model-affinity router's
3496
+ * structural client), so tests and alternative transports can substitute a
3497
+ * minimal object.
3498
+ */
3499
+ interface DraftBenchmarkClient {
3500
+ generate(req: GenerateRequestOptions & {
3501
+ stream?: false | undefined;
3502
+ }): Promise<GenerateResponse>;
3503
+ }
3504
+ /** Options for {@link compareDraftPerformance}. */
3505
+ interface CompareDraftPerformanceOptions {
3506
+ /** Model to benchmark — must be a name the target server can load. */
3507
+ readonly model: string;
3508
+ /**
3509
+ * The prompt both sides generate. The comparison is only meaningful with a
3510
+ * fixed prompt: draft acceptance — and thus speedup — is workload-dependent
3511
+ * (structured/predictable prompts gain the most; creative ones can lose).
3512
+ */
3513
+ readonly prompt: string;
3514
+ /**
3515
+ * Draft tokens per step for the speculative side. Default `4` (Ollama's
3516
+ * default for separate draft models). Must be >= 1 — the baseline side
3517
+ * always measures with drafting disabled (`0`).
3518
+ */
3519
+ readonly draftNumPredict?: number | undefined;
3520
+ /**
3521
+ * Measured runs per setting; the median is reported. Default `5`. Single
3522
+ * samples mislead — noisy small-model runs swing tens of percent.
3523
+ */
3524
+ readonly samples?: number | undefined;
3525
+ /** `num_predict` cap per measured run. Default `256`. */
3526
+ readonly numPredict?: number | undefined;
3527
+ /** Fixed sampling seed stamped on every run (with `temperature: 0`). Default `42`. */
3528
+ readonly seed?: number | undefined;
3529
+ /**
3530
+ * Run one throwaway `num_predict: 1` generation per setting before sampling
3531
+ * (pays the model load). Default `true`.
3532
+ */
3533
+ readonly warmup?: boolean | undefined;
3534
+ /**
3535
+ * `speedupMultiplier` above this reports `effectiveGain: true`. Default
3536
+ * `1.05` — five percent, past typical run-to-run noise.
3537
+ */
3538
+ readonly gainThreshold?: number | undefined;
3539
+ /** Abort signal — checked before every run and forwarded to each generate call. */
3540
+ readonly signal?: AbortSignal | undefined;
3541
+ }
3542
+ /** One side's measurement detail (see {@link DraftPerformanceComparison}). */
3543
+ interface DraftRunStats {
3544
+ /** The `draft_num_predict` value this side ran with (`0` = baseline). */
3545
+ readonly draftNumPredict: number;
3546
+ /** Median tokens/second across the valid samples (unrounded). */
3547
+ readonly medianTokensPerSecond: number;
3548
+ /** Every valid sample's tokens/second, in run order. */
3549
+ readonly sampleTokensPerSecond: readonly number[];
3550
+ /** Median generation-phase duration (`eval_duration`) in milliseconds. */
3551
+ readonly medianEvalDurationMs: number;
3552
+ }
3553
+ /** Result of {@link compareDraftPerformance}. */
3554
+ interface DraftPerformanceComparison {
3555
+ /** The model that was benchmarked. */
3556
+ readonly model: string;
3557
+ /** The prompt both sides generated. */
3558
+ readonly prompt: string;
3559
+ /** The speculative side's `draft_num_predict` (the baseline always ran `0`). */
3560
+ readonly draftNumPredict: number;
3561
+ /** Baseline median tokens/second, rounded to 1 decimal. */
3562
+ readonly baselineTokensPerSec: number;
3563
+ /** Speculative median tokens/second, rounded to 1 decimal. */
3564
+ readonly speculativeTokensPerSec: number;
3565
+ /** `speculativeTokensPerSec / baselineTokensPerSec`, rounded to 2 decimals. */
3566
+ readonly speedupMultiplier: number;
3567
+ /** `true` when the reported `speedupMultiplier` exceeds `gainThreshold`. */
3568
+ readonly effectiveGain: boolean;
3569
+ /** The threshold `effectiveGain` was evaluated against. */
3570
+ readonly gainThreshold: number;
3571
+ /** Full baseline detail (samples, medians). */
3572
+ readonly baseline: DraftRunStats;
3573
+ /** Full speculative detail (samples, medians). */
3574
+ readonly speculative: DraftRunStats;
3575
+ }
3576
+ /**
3577
+ * Measure whether speculative drafting pays off for a specific prompt:
3578
+ * identical prompt, deterministic sampling (`temperature: 0` + fixed `seed`),
3579
+ * warm-up discard, several samples per setting, median baseline
3580
+ * (`draft_num_predict: 0`) vs. median speculative (`draft_num_predict: N`)
3581
+ * compared as `speedupMultiplier`.
3582
+ *
3583
+ * Both sides run through the client's non-streaming `generate` — the raw
3584
+ * counters come back on the response and throughput is derived by
3585
+ * `formatTelemetry`. Non-positive/unusable samples are skipped; if a side
3586
+ * ends up with none, an `OllamaClientError` explains why (wrong model name /
3587
+ * empty generations) rather than reporting a fabricated zero.
3588
+ */
3589
+ declare function compareDraftPerformance(client: DraftBenchmarkClient, options: CompareDraftPerformanceOptions): Promise<DraftPerformanceComparison>;
3590
+
3446
3591
  /**
3447
3592
  * Cross-runtime helpers with no Node.js API dependency, so they stay safe to import from
3448
3593
  * the core client bundle (see ADR 0006 — Edge Runtime CI).
@@ -3717,4 +3862,4 @@ declare class ModelAffinityScheduler {
3717
3862
  /** Convenience factory — `createModelAffinityScheduler(client, opts)` reads better in setups. */
3718
3863
  declare function createModelAffinityScheduler(client: ModelAffinityClient | OllamaClient, options?: ModelAffinitySchedulerOptions): ModelAffinityScheduler;
3719
3864
 
3720
- export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, AbortableAsyncIterable, type AffinityRunningModel, Agent, type AgentChatClient, type AgentConfig, type AgentHooks, type AgentResult, type AgentRunInput, type AgentTurn, type AnswerFor, AnthropicCompatClient, AnyTool, type BackoffOptions, BalanceRequestOptions, type BalanceResponse, type BlobUploadResult, ChatRequestOptions, ChatResponse, ChatStreamResult, type ChoiceDecision, type CompactionOptions, type ContextCheck, type ContextCheckOptions, type ContextDiscoveryRequestOptions, type ContextLengthSignals, type ContextLengthSource, type ContextWindowSource, type ConversationSendOptions, ConversationSession, type ConversationSessionOptions, CopyRequestOptions, type CreateModelFromGgufOptions, CreateRequestOptions, type CumulativeCacheStats, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_CONTEXT_WARNING_THRESHOLD, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_EMBED_CONCURRENCY, DEFAULT_FAILOVER_CODES, DEFAULT_FETCH_BACKOFF_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, type Decision, type DefineToolOptions, DeleteRequestOptions, type DiscoveredContextLength, type EmbedBatchOptions, type EmbedBatchResult, EmbedRequestOptions, EmbedResponse, EmbeddingsRequestOptions, EmbeddingsResponse, type EndpointHealth, type EndpointHealthCheckResult, EndpointRegistry, type EndpointRegistryOptions, FetchLike, type FetchWithBackoffConfig, FormatOption, type FormattedTelemetry, GEN_AI_SYSTEM_OLLAMA, GenerateRequestOptions, GenerateResponse, GenerateStreamResult, type HistorySanitizeOptions, HttpClient, IMAGE_TOKEN_ESTIMATE, type IncludedBalance, KEEP_ALIVE_INDEFINITE, KEEP_ALIVE_UNLOAD, KNOWN_DONE_REASONS, type KeepAlive, type KnownDoneReason, type LegacyBalanceLimit, type LegacyIncludedBalance, type LoadMcpToolsOptions, Logger, MAX_SYSTEM_ONE_IMAGES_BYTES, MAX_SYSTEM_ONE_REQUEST_BYTES, MESSAGE_OVERHEAD_TOKENS, McpBridge, type McpBridgeOptions, McpCallToolResult, McpClientLike, McpTask, McpToolDescriptor, Message, Middleware, type ModelAffinityClient, ModelAffinityRouter, type ModelAffinityRoutingOptions, ModelAffinityScheduler, type ModelAffinitySchedulerOptions, type ModelAffinitySnapshot, type ModelAffinityStats, type ModelCapabilities, ModelOptions, ModelResponse, ModelsClient, type NoulDecision, OLLAMA_CLOUD_BASE_URL, OLLAMA_FALLBACK_CONTEXT_LENGTH, OllamaClient, type OllamaClientConfig, type OllamaCloudFreeTierQuotaBudgets, type OllamaCredentialConfig, type OllamaEndpoint, OllamaStream, OllamaStreamEvent, OllamaStreamEventType, OpenAICompatClient, type OversizedEmbedInput, type ParallelToolCallBehavior, PingResult, ProgressResponse, ProgressStreamResult, PsResponse, PullRequestOptions, PushRequestOptions, QuotaManager, type QuotaManagerOptions, type QuotaWindowConfig, type QuotaWindowStatus, RETRYABLE_STATUS_CODES, type RankResult, type RawOllamaMetrics, RawUsageSource, RequestCancellationOptions, RequestLifecycleHook, RequestRunner, type ResponsesCreateRequest, type ResponsesCreateResponse, ResponsesModule, type ResponsesReasoningEffort, type ResponsesStreamEvent, type ResponsesToolDefinition, type ResponsesUsage, type RetryConfig, type RuntimeMode, type ScoreDecision, type SessionCompactionOptions, type SessionCompactionResult, type SessionTurn, ShowRequestOptions, ShowResponse, type SpanAttributeValue, type SpanAttributes, StatusResponse, type SupportedSchema, type SystemOneAnswer, type SystemOneAnswers, SystemOneChoiceAnswer, SystemOneChoiceQuestion, SystemOneContent, SystemOneNoulAnswer, SystemOneNoulQuestion, type SystemOneQuestion, type SystemOneQuestions, type SystemOneRequest, type SystemOneResponse, SystemOneScoreAnswer, SystemOneScoreQuestion, type SystemOneUsage, ThinkValue, ThinkingMetadata, type TimeoutSignal, TokenUsage, Tool, ToolCall, ToolCallAccumulator, ToolDefinition, ToolExecutionResult, ToolHandler, ToolParameters, ToolRegistry, type TurnCacheStats, type UsageBucket, type UsageMetrics, UsageRequestOptions, type UsageResponse, VersionResponse, VisionInput, WEB_FETCH_TOOL_NAME, WEB_SEARCH_TOOL_NAME, WebFetchRequestOptions, WebFetchResponse, type WebFetchToolParams, WebSearchRequestOptions, WebSearchResponse, type WebSearchToolParams, type WebToolsClient, type WebToolsOptions, addSpanEvent, batchEmbed, calculateBackoff, canonicalToolCallSignature, checkChatContext, checkEndpointHealth, checkGenerateContext, compactConversationHistory, contextWarningMessage, createDecision, createModelAffinityScheduler, createOllamaCloudFreeTierQuota, createTimeoutSignal, createWebFetchTool, createWebSearchTool, createWebTools, defineTool, detectModelCapabilities, embedBatchOverflowMessage, encodeImage, estimateChatRequestTokens, estimateGenerateRequestTokens, estimateMessageTokens, estimateSystemOneRequestBytes, estimateTokens, extractContextLength as extractNativeContextLength, extractParameterNumCtx, fetchWithBackoff, findOversizedEmbedInputs, findRunningModelContextLength, formatTelemetry, formatWebFetchResult, formatWebSearchResult, inferRuntimeMode, isKeepAliveSugar, isKnownDoneReason, isModelResident, isSameToolCall, listAllMcpTools, listAvailableModels, loadMcpTools, mergeToolCall, mergeToolCallArgumentsString, mergeToolCallArrays, normalizeChatStream, normalizeGenerateStream, normalizeKeepAlive, normalizeProgressStream, parseNdjsonStream, parseStructuredOutput, registerMcpTools, resolveContextLength, resolveCredentialEndpoints, sanitizeHistoryForNextTurn, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };
3865
+ export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, AbortableAsyncIterable, type AffinityRunningModel, Agent, type AgentChatClient, type AgentConfig, type AgentHooks, type AgentResult, type AgentRunInput, type AgentTurn, type AnswerFor, AnthropicCompatClient, AnyTool, type BackoffOptions, BalanceRequestOptions, type BalanceResponse, type BlobUploadResult, ChatRequestOptions, ChatResponse, ChatStreamResult, type ChoiceDecision, type CompactionOptions, type CompareDraftPerformanceOptions, type ContextCheck, type ContextCheckOptions, type ContextDiscoveryRequestOptions, type ContextLengthSignals, type ContextLengthSource, type ContextWindowSource, type ConversationSendOptions, ConversationSession, type ConversationSessionOptions, CopyRequestOptions, type CreateModelFromGgufOptions, CreateRequestOptions, type CumulativeCacheStats, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_CONTEXT_WARNING_THRESHOLD, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_EMBED_CONCURRENCY, DEFAULT_FAILOVER_CODES, DEFAULT_FETCH_BACKOFF_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, type Decision, type DefineToolOptions, DeleteRequestOptions, type DiscoveredContextLength, type DraftBenchmarkClient, type DraftPerformanceComparison, type DraftRunStats, type EmbedBatchOptions, type EmbedBatchResult, EmbedRequestOptions, EmbedResponse, EmbeddingsRequestOptions, EmbeddingsResponse, type EndpointHealth, type EndpointHealthCheckResult, EndpointRegistry, type EndpointRegistryOptions, FetchLike, type FetchWithBackoffConfig, FormatOption, type FormattedTelemetry, GEN_AI_SYSTEM_OLLAMA, GenerateRequestOptions, GenerateResponse, GenerateStreamResult, type HistorySanitizeOptions, HttpClient, IMAGE_TOKEN_ESTIMATE, type IncludedBalance, KEEP_ALIVE_INDEFINITE, KEEP_ALIVE_UNLOAD, KNOWN_DONE_REASONS, type KeepAlive, type KnownDoneReason, type LegacyBalanceLimit, type LegacyIncludedBalance, type LoadMcpToolsOptions, Logger, MAX_SYSTEM_ONE_IMAGES_BYTES, MAX_SYSTEM_ONE_REQUEST_BYTES, MESSAGE_OVERHEAD_TOKENS, McpBridge, type McpBridgeOptions, McpCallToolResult, McpClientLike, McpTask, McpToolDescriptor, Message, Middleware, type ModelAffinityClient, ModelAffinityRouter, type ModelAffinityRoutingOptions, ModelAffinityScheduler, type ModelAffinitySchedulerOptions, type ModelAffinitySnapshot, type ModelAffinityStats, type ModelCapabilities, ModelOptions, ModelResponse, ModelsClient, type NoulDecision, OLLAMA_CLOUD_BASE_URL, OLLAMA_FALLBACK_CONTEXT_LENGTH, OllamaClient, type OllamaClientConfig, type OllamaCloudFreeTierQuotaBudgets, type OllamaCredentialConfig, type OllamaEndpoint, OllamaStream, OllamaStreamEvent, OllamaStreamEventType, OpenAICompatClient, type OversizedEmbedInput, type ParallelToolCallBehavior, PingResult, ProgressResponse, ProgressStreamResult, PsResponse, PullRequestOptions, PushRequestOptions, QuotaManager, type QuotaManagerOptions, type QuotaWindowConfig, type QuotaWindowStatus, RETRYABLE_STATUS_CODES, type RankResult, type RawOllamaMetrics, RawUsageSource, RequestCancellationOptions, RequestLifecycleHook, RequestRunner, type ResponsesCreateRequest, type ResponsesCreateResponse, ResponsesModule, type ResponsesReasoningEffort, type ResponsesStreamEvent, type ResponsesToolDefinition, type ResponsesUsage, type RetryConfig, type RuntimeMode, type ScoreDecision, type SessionCompactionOptions, type SessionCompactionResult, type SessionTurn, ShowRequestOptions, ShowResponse, type SpanAttributeValue, type SpanAttributes, StatusResponse, type SupportedSchema, type SystemOneAnswer, type SystemOneAnswers, SystemOneChoiceAnswer, SystemOneChoiceQuestion, SystemOneContent, SystemOneNoulAnswer, SystemOneNoulQuestion, type SystemOneQuestion, type SystemOneQuestions, type SystemOneRequest, type SystemOneResponse, SystemOneScoreAnswer, SystemOneScoreQuestion, type SystemOneUsage, ThinkValue, ThinkingMetadata, type TimeoutSignal, TokenUsage, Tool, ToolCall, ToolCallAccumulator, ToolDefinition, ToolExecutionResult, ToolHandler, ToolParameters, ToolRegistry, type TurnCacheStats, type UsageBucket, type UsageMetrics, UsageRequestOptions, type UsageResponse, VersionResponse, VisionInput, WEB_FETCH_TOOL_NAME, WEB_SEARCH_TOOL_NAME, WebFetchRequestOptions, WebFetchResponse, type WebFetchToolParams, WebSearchRequestOptions, WebSearchResponse, type WebSearchToolParams, type WebToolsClient, type WebToolsOptions, addSpanEvent, batchEmbed, calculateBackoff, canonicalToolCallSignature, checkChatContext, checkEndpointHealth, checkGenerateContext, compactConversationHistory, compareDraftPerformance, contextWarningMessage, createDecision, createModelAffinityScheduler, createOllamaCloudFreeTierQuota, createTimeoutSignal, createWebFetchTool, createWebSearchTool, createWebTools, defineTool, detectModelCapabilities, embedBatchOverflowMessage, encodeImage, estimateChatRequestTokens, estimateGenerateRequestTokens, estimateMessageTokens, estimateSystemOneRequestBytes, estimateTokens, extractContextLength as extractNativeContextLength, extractParameterNumCtx, fetchWithBackoff, findOversizedEmbedInputs, findRunningModelContextLength, formatTelemetry, formatWebFetchResult, formatWebSearchResult, inferRuntimeMode, isKeepAliveSugar, isKnownDoneReason, isModelResident, isSameToolCall, listAllMcpTools, listAvailableModels, loadMcpTools, mergeToolCall, mergeToolCallArgumentsString, mergeToolCallArrays, normalizeChatStream, normalizeGenerateStream, normalizeKeepAlive, normalizeProgressStream, parseNdjsonStream, parseStructuredOutput, registerMcpTools, resolveContextLength, resolveCredentialEndpoints, sanitizeHistoryForNextTurn, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };
package/dist/index.d.ts CHANGED
@@ -3443,6 +3443,151 @@ interface FormattedTelemetry {
3443
3443
  */
3444
3444
  declare function formatTelemetry(raw: RawOllamaMetrics): FormattedTelemetry;
3445
3445
 
3446
+ /**
3447
+ * Speculative-decoding draft benchmark — measures whether the runtime
3448
+ * `draft_num_predict` option provides a net throughput gain for a specific
3449
+ * prompt (October 13 digest; see ADR 0030).
3450
+ *
3451
+ * Upstream Ollama does not emit a dedicated draft-token acceptance counter —
3452
+ * speculative efficiency is only observable as effective throughput
3453
+ * (`eval_count` / `eval_duration`). And the signal is noisy: rejected draft
3454
+ * predictions *add* compute (high-temperature, creative, non-deterministic
3455
+ * prompts), while accepted drafts dramatically cut `eval_duration` on
3456
+ * predictable workloads (structured code generation, JSON extraction). A
3457
+ * single run therefore proves nothing — this helper implements the
3458
+ * deterministic profiling protocol the upstream observations call for:
3459
+ *
3460
+ * - **Deterministic sampling** — `temperature: 0` is forced and a fixed
3461
+ * `seed` (default 42) is stamped on every measured run, so both sides
3462
+ * generate comparable token streams.
3463
+ * - **Warm-up discard** — one throwaway `num_predict: 1` generation per
3464
+ * setting pays the model load (`load_duration`) before any sample is
3465
+ * taken.
3466
+ * - **Identical prompt, both sides** — baseline (`draft_num_predict: 0`,
3467
+ * drafting disabled) vs. speculative (`draft_num_predict: N`), several
3468
+ * samples each, **median** compared (single samples mislead).
3469
+ * - Throughput comes from `formatTelemetry` — the official Usage-doc
3470
+ * tokens/second math, never hand-rolled nanosecond division.
3471
+ *
3472
+ * `effectiveGain` is true when the reported `speedupMultiplier` exceeds
3473
+ * `gainThreshold` (default 1.05 — five percent, past run-to-run noise). A
3474
+ * speedup of about 1.0 usually means the model has **no draft network** (and
3475
+ * no embedded MTP tensors): the server silently ignores the option and both
3476
+ * sides measure the same — see `lab/15-speculative-draft` for a per-setting
3477
+ * table over 0/4/8/16.
3478
+ *
3479
+ * ```ts
3480
+ * import { OllamaClient, compareDraftPerformance } from '@nemesis-oss/ollama-sdk';
3481
+ *
3482
+ * const comparison = await compareDraftPerformance(new OllamaClient(), {
3483
+ * model: 'llama3.1',
3484
+ * prompt: 'Extract the sender, subject, and date from this email as JSON: ...',
3485
+ * });
3486
+ * if (comparison.effectiveGain) {
3487
+ * // drafting pays off for this prompt
3488
+ * }
3489
+ * ```
3490
+ */
3491
+
3492
+ /**
3493
+ * Structural dependency of {@link compareDraftPerformance}: anything that can
3494
+ * run a non-streaming `/api/generate` call. `OllamaClient` satisfies it
3495
+ * without being named (same discipline as the model-affinity router's
3496
+ * structural client), so tests and alternative transports can substitute a
3497
+ * minimal object.
3498
+ */
3499
+ interface DraftBenchmarkClient {
3500
+ generate(req: GenerateRequestOptions & {
3501
+ stream?: false | undefined;
3502
+ }): Promise<GenerateResponse>;
3503
+ }
3504
+ /** Options for {@link compareDraftPerformance}. */
3505
+ interface CompareDraftPerformanceOptions {
3506
+ /** Model to benchmark — must be a name the target server can load. */
3507
+ readonly model: string;
3508
+ /**
3509
+ * The prompt both sides generate. The comparison is only meaningful with a
3510
+ * fixed prompt: draft acceptance — and thus speedup — is workload-dependent
3511
+ * (structured/predictable prompts gain the most; creative ones can lose).
3512
+ */
3513
+ readonly prompt: string;
3514
+ /**
3515
+ * Draft tokens per step for the speculative side. Default `4` (Ollama's
3516
+ * default for separate draft models). Must be >= 1 — the baseline side
3517
+ * always measures with drafting disabled (`0`).
3518
+ */
3519
+ readonly draftNumPredict?: number | undefined;
3520
+ /**
3521
+ * Measured runs per setting; the median is reported. Default `5`. Single
3522
+ * samples mislead — noisy small-model runs swing tens of percent.
3523
+ */
3524
+ readonly samples?: number | undefined;
3525
+ /** `num_predict` cap per measured run. Default `256`. */
3526
+ readonly numPredict?: number | undefined;
3527
+ /** Fixed sampling seed stamped on every run (with `temperature: 0`). Default `42`. */
3528
+ readonly seed?: number | undefined;
3529
+ /**
3530
+ * Run one throwaway `num_predict: 1` generation per setting before sampling
3531
+ * (pays the model load). Default `true`.
3532
+ */
3533
+ readonly warmup?: boolean | undefined;
3534
+ /**
3535
+ * `speedupMultiplier` above this reports `effectiveGain: true`. Default
3536
+ * `1.05` — five percent, past typical run-to-run noise.
3537
+ */
3538
+ readonly gainThreshold?: number | undefined;
3539
+ /** Abort signal — checked before every run and forwarded to each generate call. */
3540
+ readonly signal?: AbortSignal | undefined;
3541
+ }
3542
+ /** One side's measurement detail (see {@link DraftPerformanceComparison}). */
3543
+ interface DraftRunStats {
3544
+ /** The `draft_num_predict` value this side ran with (`0` = baseline). */
3545
+ readonly draftNumPredict: number;
3546
+ /** Median tokens/second across the valid samples (unrounded). */
3547
+ readonly medianTokensPerSecond: number;
3548
+ /** Every valid sample's tokens/second, in run order. */
3549
+ readonly sampleTokensPerSecond: readonly number[];
3550
+ /** Median generation-phase duration (`eval_duration`) in milliseconds. */
3551
+ readonly medianEvalDurationMs: number;
3552
+ }
3553
+ /** Result of {@link compareDraftPerformance}. */
3554
+ interface DraftPerformanceComparison {
3555
+ /** The model that was benchmarked. */
3556
+ readonly model: string;
3557
+ /** The prompt both sides generated. */
3558
+ readonly prompt: string;
3559
+ /** The speculative side's `draft_num_predict` (the baseline always ran `0`). */
3560
+ readonly draftNumPredict: number;
3561
+ /** Baseline median tokens/second, rounded to 1 decimal. */
3562
+ readonly baselineTokensPerSec: number;
3563
+ /** Speculative median tokens/second, rounded to 1 decimal. */
3564
+ readonly speculativeTokensPerSec: number;
3565
+ /** `speculativeTokensPerSec / baselineTokensPerSec`, rounded to 2 decimals. */
3566
+ readonly speedupMultiplier: number;
3567
+ /** `true` when the reported `speedupMultiplier` exceeds `gainThreshold`. */
3568
+ readonly effectiveGain: boolean;
3569
+ /** The threshold `effectiveGain` was evaluated against. */
3570
+ readonly gainThreshold: number;
3571
+ /** Full baseline detail (samples, medians). */
3572
+ readonly baseline: DraftRunStats;
3573
+ /** Full speculative detail (samples, medians). */
3574
+ readonly speculative: DraftRunStats;
3575
+ }
3576
+ /**
3577
+ * Measure whether speculative drafting pays off for a specific prompt:
3578
+ * identical prompt, deterministic sampling (`temperature: 0` + fixed `seed`),
3579
+ * warm-up discard, several samples per setting, median baseline
3580
+ * (`draft_num_predict: 0`) vs. median speculative (`draft_num_predict: N`)
3581
+ * compared as `speedupMultiplier`.
3582
+ *
3583
+ * Both sides run through the client's non-streaming `generate` — the raw
3584
+ * counters come back on the response and throughput is derived by
3585
+ * `formatTelemetry`. Non-positive/unusable samples are skipped; if a side
3586
+ * ends up with none, an `OllamaClientError` explains why (wrong model name /
3587
+ * empty generations) rather than reporting a fabricated zero.
3588
+ */
3589
+ declare function compareDraftPerformance(client: DraftBenchmarkClient, options: CompareDraftPerformanceOptions): Promise<DraftPerformanceComparison>;
3590
+
3446
3591
  /**
3447
3592
  * Cross-runtime helpers with no Node.js API dependency, so they stay safe to import from
3448
3593
  * the core client bundle (see ADR 0006 — Edge Runtime CI).
@@ -3717,4 +3862,4 @@ declare class ModelAffinityScheduler {
3717
3862
  /** Convenience factory — `createModelAffinityScheduler(client, opts)` reads better in setups. */
3718
3863
  declare function createModelAffinityScheduler(client: ModelAffinityClient | OllamaClient, options?: ModelAffinitySchedulerOptions): ModelAffinityScheduler;
3719
3864
 
3720
- export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, AbortableAsyncIterable, type AffinityRunningModel, Agent, type AgentChatClient, type AgentConfig, type AgentHooks, type AgentResult, type AgentRunInput, type AgentTurn, type AnswerFor, AnthropicCompatClient, AnyTool, type BackoffOptions, BalanceRequestOptions, type BalanceResponse, type BlobUploadResult, ChatRequestOptions, ChatResponse, ChatStreamResult, type ChoiceDecision, type CompactionOptions, type ContextCheck, type ContextCheckOptions, type ContextDiscoveryRequestOptions, type ContextLengthSignals, type ContextLengthSource, type ContextWindowSource, type ConversationSendOptions, ConversationSession, type ConversationSessionOptions, CopyRequestOptions, type CreateModelFromGgufOptions, CreateRequestOptions, type CumulativeCacheStats, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_CONTEXT_WARNING_THRESHOLD, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_EMBED_CONCURRENCY, DEFAULT_FAILOVER_CODES, DEFAULT_FETCH_BACKOFF_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, type Decision, type DefineToolOptions, DeleteRequestOptions, type DiscoveredContextLength, type EmbedBatchOptions, type EmbedBatchResult, EmbedRequestOptions, EmbedResponse, EmbeddingsRequestOptions, EmbeddingsResponse, type EndpointHealth, type EndpointHealthCheckResult, EndpointRegistry, type EndpointRegistryOptions, FetchLike, type FetchWithBackoffConfig, FormatOption, type FormattedTelemetry, GEN_AI_SYSTEM_OLLAMA, GenerateRequestOptions, GenerateResponse, GenerateStreamResult, type HistorySanitizeOptions, HttpClient, IMAGE_TOKEN_ESTIMATE, type IncludedBalance, KEEP_ALIVE_INDEFINITE, KEEP_ALIVE_UNLOAD, KNOWN_DONE_REASONS, type KeepAlive, type KnownDoneReason, type LegacyBalanceLimit, type LegacyIncludedBalance, type LoadMcpToolsOptions, Logger, MAX_SYSTEM_ONE_IMAGES_BYTES, MAX_SYSTEM_ONE_REQUEST_BYTES, MESSAGE_OVERHEAD_TOKENS, McpBridge, type McpBridgeOptions, McpCallToolResult, McpClientLike, McpTask, McpToolDescriptor, Message, Middleware, type ModelAffinityClient, ModelAffinityRouter, type ModelAffinityRoutingOptions, ModelAffinityScheduler, type ModelAffinitySchedulerOptions, type ModelAffinitySnapshot, type ModelAffinityStats, type ModelCapabilities, ModelOptions, ModelResponse, ModelsClient, type NoulDecision, OLLAMA_CLOUD_BASE_URL, OLLAMA_FALLBACK_CONTEXT_LENGTH, OllamaClient, type OllamaClientConfig, type OllamaCloudFreeTierQuotaBudgets, type OllamaCredentialConfig, type OllamaEndpoint, OllamaStream, OllamaStreamEvent, OllamaStreamEventType, OpenAICompatClient, type OversizedEmbedInput, type ParallelToolCallBehavior, PingResult, ProgressResponse, ProgressStreamResult, PsResponse, PullRequestOptions, PushRequestOptions, QuotaManager, type QuotaManagerOptions, type QuotaWindowConfig, type QuotaWindowStatus, RETRYABLE_STATUS_CODES, type RankResult, type RawOllamaMetrics, RawUsageSource, RequestCancellationOptions, RequestLifecycleHook, RequestRunner, type ResponsesCreateRequest, type ResponsesCreateResponse, ResponsesModule, type ResponsesReasoningEffort, type ResponsesStreamEvent, type ResponsesToolDefinition, type ResponsesUsage, type RetryConfig, type RuntimeMode, type ScoreDecision, type SessionCompactionOptions, type SessionCompactionResult, type SessionTurn, ShowRequestOptions, ShowResponse, type SpanAttributeValue, type SpanAttributes, StatusResponse, type SupportedSchema, type SystemOneAnswer, type SystemOneAnswers, SystemOneChoiceAnswer, SystemOneChoiceQuestion, SystemOneContent, SystemOneNoulAnswer, SystemOneNoulQuestion, type SystemOneQuestion, type SystemOneQuestions, type SystemOneRequest, type SystemOneResponse, SystemOneScoreAnswer, SystemOneScoreQuestion, type SystemOneUsage, ThinkValue, ThinkingMetadata, type TimeoutSignal, TokenUsage, Tool, ToolCall, ToolCallAccumulator, ToolDefinition, ToolExecutionResult, ToolHandler, ToolParameters, ToolRegistry, type TurnCacheStats, type UsageBucket, type UsageMetrics, UsageRequestOptions, type UsageResponse, VersionResponse, VisionInput, WEB_FETCH_TOOL_NAME, WEB_SEARCH_TOOL_NAME, WebFetchRequestOptions, WebFetchResponse, type WebFetchToolParams, WebSearchRequestOptions, WebSearchResponse, type WebSearchToolParams, type WebToolsClient, type WebToolsOptions, addSpanEvent, batchEmbed, calculateBackoff, canonicalToolCallSignature, checkChatContext, checkEndpointHealth, checkGenerateContext, compactConversationHistory, contextWarningMessage, createDecision, createModelAffinityScheduler, createOllamaCloudFreeTierQuota, createTimeoutSignal, createWebFetchTool, createWebSearchTool, createWebTools, defineTool, detectModelCapabilities, embedBatchOverflowMessage, encodeImage, estimateChatRequestTokens, estimateGenerateRequestTokens, estimateMessageTokens, estimateSystemOneRequestBytes, estimateTokens, extractContextLength as extractNativeContextLength, extractParameterNumCtx, fetchWithBackoff, findOversizedEmbedInputs, findRunningModelContextLength, formatTelemetry, formatWebFetchResult, formatWebSearchResult, inferRuntimeMode, isKeepAliveSugar, isKnownDoneReason, isModelResident, isSameToolCall, listAllMcpTools, listAvailableModels, loadMcpTools, mergeToolCall, mergeToolCallArgumentsString, mergeToolCallArrays, normalizeChatStream, normalizeGenerateStream, normalizeKeepAlive, normalizeProgressStream, parseNdjsonStream, parseStructuredOutput, registerMcpTools, resolveContextLength, resolveCredentialEndpoints, sanitizeHistoryForNextTurn, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };
3865
+ export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, AbortableAsyncIterable, type AffinityRunningModel, Agent, type AgentChatClient, type AgentConfig, type AgentHooks, type AgentResult, type AgentRunInput, type AgentTurn, type AnswerFor, AnthropicCompatClient, AnyTool, type BackoffOptions, BalanceRequestOptions, type BalanceResponse, type BlobUploadResult, ChatRequestOptions, ChatResponse, ChatStreamResult, type ChoiceDecision, type CompactionOptions, type CompareDraftPerformanceOptions, type ContextCheck, type ContextCheckOptions, type ContextDiscoveryRequestOptions, type ContextLengthSignals, type ContextLengthSource, type ContextWindowSource, type ConversationSendOptions, ConversationSession, type ConversationSessionOptions, CopyRequestOptions, type CreateModelFromGgufOptions, CreateRequestOptions, type CumulativeCacheStats, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_CONTEXT_WARNING_THRESHOLD, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_EMBED_CONCURRENCY, DEFAULT_FAILOVER_CODES, DEFAULT_FETCH_BACKOFF_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, type Decision, type DefineToolOptions, DeleteRequestOptions, type DiscoveredContextLength, type DraftBenchmarkClient, type DraftPerformanceComparison, type DraftRunStats, type EmbedBatchOptions, type EmbedBatchResult, EmbedRequestOptions, EmbedResponse, EmbeddingsRequestOptions, EmbeddingsResponse, type EndpointHealth, type EndpointHealthCheckResult, EndpointRegistry, type EndpointRegistryOptions, FetchLike, type FetchWithBackoffConfig, FormatOption, type FormattedTelemetry, GEN_AI_SYSTEM_OLLAMA, GenerateRequestOptions, GenerateResponse, GenerateStreamResult, type HistorySanitizeOptions, HttpClient, IMAGE_TOKEN_ESTIMATE, type IncludedBalance, KEEP_ALIVE_INDEFINITE, KEEP_ALIVE_UNLOAD, KNOWN_DONE_REASONS, type KeepAlive, type KnownDoneReason, type LegacyBalanceLimit, type LegacyIncludedBalance, type LoadMcpToolsOptions, Logger, MAX_SYSTEM_ONE_IMAGES_BYTES, MAX_SYSTEM_ONE_REQUEST_BYTES, MESSAGE_OVERHEAD_TOKENS, McpBridge, type McpBridgeOptions, McpCallToolResult, McpClientLike, McpTask, McpToolDescriptor, Message, Middleware, type ModelAffinityClient, ModelAffinityRouter, type ModelAffinityRoutingOptions, ModelAffinityScheduler, type ModelAffinitySchedulerOptions, type ModelAffinitySnapshot, type ModelAffinityStats, type ModelCapabilities, ModelOptions, ModelResponse, ModelsClient, type NoulDecision, OLLAMA_CLOUD_BASE_URL, OLLAMA_FALLBACK_CONTEXT_LENGTH, OllamaClient, type OllamaClientConfig, type OllamaCloudFreeTierQuotaBudgets, type OllamaCredentialConfig, type OllamaEndpoint, OllamaStream, OllamaStreamEvent, OllamaStreamEventType, OpenAICompatClient, type OversizedEmbedInput, type ParallelToolCallBehavior, PingResult, ProgressResponse, ProgressStreamResult, PsResponse, PullRequestOptions, PushRequestOptions, QuotaManager, type QuotaManagerOptions, type QuotaWindowConfig, type QuotaWindowStatus, RETRYABLE_STATUS_CODES, type RankResult, type RawOllamaMetrics, RawUsageSource, RequestCancellationOptions, RequestLifecycleHook, RequestRunner, type ResponsesCreateRequest, type ResponsesCreateResponse, ResponsesModule, type ResponsesReasoningEffort, type ResponsesStreamEvent, type ResponsesToolDefinition, type ResponsesUsage, type RetryConfig, type RuntimeMode, type ScoreDecision, type SessionCompactionOptions, type SessionCompactionResult, type SessionTurn, ShowRequestOptions, ShowResponse, type SpanAttributeValue, type SpanAttributes, StatusResponse, type SupportedSchema, type SystemOneAnswer, type SystemOneAnswers, SystemOneChoiceAnswer, SystemOneChoiceQuestion, SystemOneContent, SystemOneNoulAnswer, SystemOneNoulQuestion, type SystemOneQuestion, type SystemOneQuestions, type SystemOneRequest, type SystemOneResponse, SystemOneScoreAnswer, SystemOneScoreQuestion, type SystemOneUsage, ThinkValue, ThinkingMetadata, type TimeoutSignal, TokenUsage, Tool, ToolCall, ToolCallAccumulator, ToolDefinition, ToolExecutionResult, ToolHandler, ToolParameters, ToolRegistry, type TurnCacheStats, type UsageBucket, type UsageMetrics, UsageRequestOptions, type UsageResponse, VersionResponse, VisionInput, WEB_FETCH_TOOL_NAME, WEB_SEARCH_TOOL_NAME, WebFetchRequestOptions, WebFetchResponse, type WebFetchToolParams, WebSearchRequestOptions, WebSearchResponse, type WebSearchToolParams, type WebToolsClient, type WebToolsOptions, addSpanEvent, batchEmbed, calculateBackoff, canonicalToolCallSignature, checkChatContext, checkEndpointHealth, checkGenerateContext, compactConversationHistory, compareDraftPerformance, contextWarningMessage, createDecision, createModelAffinityScheduler, createOllamaCloudFreeTierQuota, createTimeoutSignal, createWebFetchTool, createWebSearchTool, createWebTools, defineTool, detectModelCapabilities, embedBatchOverflowMessage, encodeImage, estimateChatRequestTokens, estimateGenerateRequestTokens, estimateMessageTokens, estimateSystemOneRequestBytes, estimateTokens, extractContextLength as extractNativeContextLength, extractParameterNumCtx, fetchWithBackoff, findOversizedEmbedInputs, findRunningModelContextLength, formatTelemetry, formatWebFetchResult, formatWebSearchResult, inferRuntimeMode, isKeepAliveSugar, isKnownDoneReason, isModelResident, isSameToolCall, listAllMcpTools, listAvailableModels, loadMcpTools, mergeToolCall, mergeToolCallArgumentsString, mergeToolCallArrays, normalizeChatStream, normalizeGenerateStream, normalizeKeepAlive, normalizeProgressStream, parseNdjsonStream, parseStructuredOutput, registerMcpTools, resolveContextLength, resolveCredentialEndpoints, sanitizeHistoryForNextTurn, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };
package/dist/index.js CHANGED
@@ -7748,6 +7748,111 @@ var Agent = class {
7748
7748
  }
7749
7749
  };
7750
7750
 
7751
+ // src/draft-benchmark.ts
7752
+ var DEFAULT_DRAFT_TOKENS = 4;
7753
+ var DEFAULT_SAMPLES = 5;
7754
+ var DEFAULT_NUM_PREDICT = 256;
7755
+ var DEFAULT_SEED = 42;
7756
+ var DEFAULT_GAIN_THRESHOLD = 1.05;
7757
+ function median(values) {
7758
+ const sorted = [...values].sort((a, b) => a - b);
7759
+ const mid = Math.floor(sorted.length / 2);
7760
+ return sorted.length % 2 === 1 ? sorted[mid] ?? 0 : ((sorted[mid - 1] ?? 0) + (sorted[mid] ?? 0)) / 2;
7761
+ }
7762
+ function roundTo(value, decimals) {
7763
+ const factor = 10 ** decimals;
7764
+ return Math.round(value * factor) / factor;
7765
+ }
7766
+ function requirePositiveInteger(name, value) {
7767
+ if (!Number.isInteger(value) || value < 1) {
7768
+ throw new RangeError(
7769
+ `compareDraftPerformance: ${name} must be an integer >= 1 (got ${value}).`
7770
+ );
7771
+ }
7772
+ }
7773
+ async function runSide(client, options, draftNumPredict, samples, numPredict, seed) {
7774
+ const samplingOptions = {
7775
+ num_predict: numPredict,
7776
+ temperature: 0,
7777
+ seed,
7778
+ draft_num_predict: draftNumPredict
7779
+ };
7780
+ if (options.warmup ?? true) {
7781
+ await client.generate({
7782
+ model: options.model,
7783
+ prompt: options.prompt,
7784
+ options: { ...samplingOptions, num_predict: 1 },
7785
+ stream: false,
7786
+ signal: options.signal
7787
+ });
7788
+ }
7789
+ const tokensPerSecond = [];
7790
+ const generationMs = [];
7791
+ for (let i = 0; i < samples; i++) {
7792
+ options.signal?.throwIfAborted();
7793
+ const response = await client.generate({
7794
+ model: options.model,
7795
+ prompt: options.prompt,
7796
+ options: samplingOptions,
7797
+ stream: false,
7798
+ signal: options.signal
7799
+ });
7800
+ const telemetry = formatTelemetry(response);
7801
+ if (Number.isFinite(telemetry.tokensPerSecond) && telemetry.tokensPerSecond > 0) {
7802
+ tokensPerSecond.push(telemetry.tokensPerSecond);
7803
+ generationMs.push(telemetry.generationMs);
7804
+ }
7805
+ }
7806
+ if (tokensPerSecond.length === 0) {
7807
+ throw new OllamaClientError(
7808
+ `compareDraftPerformance: every draft_num_predict=${draftNumPredict} sample returned a non-positive tokens/second. The model name is likely wrong, or the server returned empty generations.`,
7809
+ { code: "draft_benchmark_no_valid_samples" }
7810
+ );
7811
+ }
7812
+ return {
7813
+ draftNumPredict,
7814
+ medianTokensPerSecond: median(tokensPerSecond),
7815
+ sampleTokensPerSecond: tokensPerSecond,
7816
+ medianEvalDurationMs: median(generationMs)
7817
+ };
7818
+ }
7819
+ async function compareDraftPerformance(client, options) {
7820
+ const draftNumPredict = options.draftNumPredict ?? DEFAULT_DRAFT_TOKENS;
7821
+ requirePositiveInteger("draftNumPredict", draftNumPredict);
7822
+ const samples = options.samples ?? DEFAULT_SAMPLES;
7823
+ requirePositiveInteger("samples", samples);
7824
+ const numPredict = options.numPredict ?? DEFAULT_NUM_PREDICT;
7825
+ requirePositiveInteger("numPredict", numPredict);
7826
+ const seed = options.seed ?? DEFAULT_SEED;
7827
+ if (!Number.isInteger(seed)) {
7828
+ throw new RangeError(`compareDraftPerformance: seed must be an integer (got ${seed}).`);
7829
+ }
7830
+ const gainThreshold = options.gainThreshold ?? DEFAULT_GAIN_THRESHOLD;
7831
+ if (!Number.isFinite(gainThreshold) || gainThreshold <= 0) {
7832
+ throw new RangeError(
7833
+ `compareDraftPerformance: gainThreshold must be a positive finite number (got ${gainThreshold}).`
7834
+ );
7835
+ }
7836
+ options.signal?.throwIfAborted();
7837
+ const baseline = await runSide(client, options, 0, samples, numPredict, seed);
7838
+ const speculative = await runSide(client, options, draftNumPredict, samples, numPredict, seed);
7839
+ const baselineTokensPerSec = roundTo(baseline.medianTokensPerSecond, 1);
7840
+ const speculativeTokensPerSec = roundTo(speculative.medianTokensPerSecond, 1);
7841
+ const speedupMultiplier = baselineTokensPerSec > 0 ? roundTo(speculativeTokensPerSec / baselineTokensPerSec, 2) : 0;
7842
+ return {
7843
+ model: options.model,
7844
+ prompt: options.prompt,
7845
+ draftNumPredict,
7846
+ baselineTokensPerSec,
7847
+ speculativeTokensPerSec,
7848
+ speedupMultiplier,
7849
+ effectiveGain: speedupMultiplier > gainThreshold,
7850
+ gainThreshold,
7851
+ baseline,
7852
+ speculative
7853
+ };
7854
+ }
7855
+
7751
7856
  // src/affinity.ts
7752
7857
  var DEFAULT_CONCURRENT_MODELS = 1;
7753
7858
  var DEFAULT_PER_MODEL_CONCURRENCY = 1;
@@ -7909,6 +8014,6 @@ function estimateSystemOneRequestBytes(request) {
7909
8014
  return new TextEncoder().encode(JSON.stringify(request)).byteLength;
7910
8015
  }
7911
8016
 
7912
- export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, Agent, AnthropicCompatClient, AnthropicMessagesStream, ConversationSession, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_CONTEXT_WARNING_THRESHOLD, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_EMBED_CONCURRENCY, DEFAULT_FAILOVER_CODES, DEFAULT_FETCH_BACKOFF_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, EndpointRegistry, GEN_AI_SYSTEM_OLLAMA, HttpClient, IMAGE_TOKEN_ESTIMATE, KEEP_ALIVE_INDEFINITE, KEEP_ALIVE_UNLOAD, KNOWN_DONE_REASONS, MAX_SYSTEM_ONE_IMAGES_BYTES, MAX_SYSTEM_ONE_REQUEST_BYTES, MESSAGE_OVERHEAD_TOKENS, McpBridge, ModelAffinityRouter, ModelAffinityScheduler, ModelsClient, NANOS_PER_MS, NANOS_PER_SECOND, NOOP_LOGGER, OLLAMA_CLOUD_BASE_URL, OLLAMA_FALLBACK_CONTEXT_LENGTH, OllamaAbortError, OllamaAgentMaxIterationsError, OllamaAgentMaxToolCallsError, OllamaAgentToolLoopError, OllamaAuthError, OllamaBadGatewayError, OllamaClient, OllamaClientError, OllamaGenericClientError, OllamaIncompatibleModelError, OllamaMcpError, OllamaModelRoutingError, OllamaNetworkError, OllamaNotFoundError, OllamaOverloadedError, OllamaQuotaExceededError, OllamaRateLimitError, OllamaRequestTooLargeError, OllamaRequestValidationError, OllamaResponseValidationError, OllamaServerError, OllamaServerVersionUnknownError, OllamaSkillInvalidError, OllamaSkillNotFoundError, OllamaStream, OllamaStreamError, OllamaTimeoutError, OllamaToolTimeoutError, OllamaToolValidationError, OllamaUnsupportedCapabilityError, OpenAIChatCompletionStream, OpenAICompatClient, OpenAICompletionStream, OpenAIResponsesStream, OpenAIResponsesStreamError, QuotaManager, RETRYABLE_STATUS_CODES, ResponsesModule, ToolCallAccumulator, ToolRegistry, WEB_FETCH_TOOL_NAME, WEB_SEARCH_TOOL_NAME, addSpanEvent, applySkill, batchEmbed, calculateBackoff, canonicalToolCallSignature, checkChatContext, checkEndpointHealth, checkGenerateContext, compactConversationHistory, composeMiddleware, contextWarningMessage, createConsoleLogger, createDecision, createModelAffinityScheduler, createOllamaCloudFreeTierQuota, createTimeoutSignal, createWebFetchTool, createWebSearchTool, createWebTools, defineTool, detectModelCapabilities, embedBatchOverflowMessage, encodeImage, estimateChatRequestTokens, estimateGenerateRequestTokens, estimateMessageTokens, estimateSystemOneRequestBytes, estimateTokens, extractContextLength as extractNativeContextLength, extractParameterNumCtx, extractUsage, fetchWithBackoff, findOversizedEmbedInputs, findRunningModelContextLength, formatTelemetry, formatWebFetchResult, formatWebSearchResult, imageStringNeedsResolution, inferRuntimeMode, isKeepAliveSugar, isKnownDoneReason, isModelResident, isSameToolCall, listAllMcpTools, listAvailableModels, loadMcpTools, mapError, mergeToolCall, mergeToolCallArgumentsString, mergeToolCallArrays, normalizeChatStream, normalizeGenerateStream, normalizeKeepAlive, normalizeProgressStream, parseFrontmatter, parseNdjsonStream, parseSseStream, parseStructuredOutput, registerMcpTools, resolveContextLength, resolveCredentialEndpoints, resolveImageInput, sanitizeHistoryForNextTurn, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };
8017
+ export { ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_TOOL_CALL_ID, ATTR_GEN_AI_TOOL_NAME, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_OLLAMA_AGENT_ITERATION, ATTR_OLLAMA_AGENT_MAX_ITERATIONS, ATTR_OLLAMA_ENDPOINT_ATTEMPT, ATTR_OLLAMA_ENDPOINT_NAME, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL, Agent, AnthropicCompatClient, AnthropicMessagesStream, ConversationSession, DEFAULT_BACKOFF, DEFAULT_BASE_URL, DEFAULT_CONTEXT_WARNING_THRESHOLD, DEFAULT_EMBED_BATCH_SIZE, DEFAULT_EMBED_CONCURRENCY, DEFAULT_FAILOVER_CODES, DEFAULT_FETCH_BACKOFF_CONFIG, DEFAULT_RETRY_CONFIG, DEFAULT_TIMEOUT_MS, EndpointRegistry, GEN_AI_SYSTEM_OLLAMA, HttpClient, IMAGE_TOKEN_ESTIMATE, KEEP_ALIVE_INDEFINITE, KEEP_ALIVE_UNLOAD, KNOWN_DONE_REASONS, MAX_SYSTEM_ONE_IMAGES_BYTES, MAX_SYSTEM_ONE_REQUEST_BYTES, MESSAGE_OVERHEAD_TOKENS, McpBridge, ModelAffinityRouter, ModelAffinityScheduler, ModelsClient, NANOS_PER_MS, NANOS_PER_SECOND, NOOP_LOGGER, OLLAMA_CLOUD_BASE_URL, OLLAMA_FALLBACK_CONTEXT_LENGTH, OllamaAbortError, OllamaAgentMaxIterationsError, OllamaAgentMaxToolCallsError, OllamaAgentToolLoopError, OllamaAuthError, OllamaBadGatewayError, OllamaClient, OllamaClientError, OllamaGenericClientError, OllamaIncompatibleModelError, OllamaMcpError, OllamaModelRoutingError, OllamaNetworkError, OllamaNotFoundError, OllamaOverloadedError, OllamaQuotaExceededError, OllamaRateLimitError, OllamaRequestTooLargeError, OllamaRequestValidationError, OllamaResponseValidationError, OllamaServerError, OllamaServerVersionUnknownError, OllamaSkillInvalidError, OllamaSkillNotFoundError, OllamaStream, OllamaStreamError, OllamaTimeoutError, OllamaToolTimeoutError, OllamaToolValidationError, OllamaUnsupportedCapabilityError, OpenAIChatCompletionStream, OpenAICompatClient, OpenAICompletionStream, OpenAIResponsesStream, OpenAIResponsesStreamError, QuotaManager, RETRYABLE_STATUS_CODES, ResponsesModule, ToolCallAccumulator, ToolRegistry, WEB_FETCH_TOOL_NAME, WEB_SEARCH_TOOL_NAME, addSpanEvent, applySkill, batchEmbed, calculateBackoff, canonicalToolCallSignature, checkChatContext, checkEndpointHealth, checkGenerateContext, compactConversationHistory, compareDraftPerformance, composeMiddleware, contextWarningMessage, createConsoleLogger, createDecision, createModelAffinityScheduler, createOllamaCloudFreeTierQuota, createTimeoutSignal, createWebFetchTool, createWebSearchTool, createWebTools, defineTool, detectModelCapabilities, embedBatchOverflowMessage, encodeImage, estimateChatRequestTokens, estimateGenerateRequestTokens, estimateMessageTokens, estimateSystemOneRequestBytes, estimateTokens, extractContextLength as extractNativeContextLength, extractParameterNumCtx, extractUsage, fetchWithBackoff, findOversizedEmbedInputs, findRunningModelContextLength, formatTelemetry, formatWebFetchResult, formatWebSearchResult, imageStringNeedsResolution, inferRuntimeMode, isKeepAliveSugar, isKnownDoneReason, isModelResident, isSameToolCall, listAllMcpTools, listAvailableModels, loadMcpTools, mapError, mergeToolCall, mergeToolCallArgumentsString, mergeToolCallArrays, normalizeChatStream, normalizeGenerateStream, normalizeKeepAlive, normalizeProgressStream, parseFrontmatter, parseNdjsonStream, parseSseStream, parseStructuredOutput, registerMcpTools, resolveContextLength, resolveCredentialEndpoints, resolveImageInput, sanitizeHistoryForNextTurn, setSpanError, toDataStream, toResponse, toTextStream, withRetry, withSpan, zodToJsonSchema };
7913
8018
  //# sourceMappingURL=index.js.map
7914
8019
  //# sourceMappingURL=index.js.map