smoltalk 0.0.37 → 0.0.39

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.
Files changed (40) hide show
  1. package/dist/classes/message/AssistantMessage.d.ts +9 -1
  2. package/dist/classes/message/AssistantMessage.js +14 -0
  3. package/dist/classes/message/index.d.ts +3 -1
  4. package/dist/client.js +3 -4
  5. package/dist/clients/anthropic.d.ts +2 -1
  6. package/dist/clients/anthropic.js +34 -15
  7. package/dist/clients/baseClient.d.ts +6 -0
  8. package/dist/clients/baseClient.js +131 -7
  9. package/dist/clients/google.d.ts +2 -1
  10. package/dist/clients/google.js +29 -7
  11. package/dist/clients/ollama.d.ts +2 -1
  12. package/dist/clients/ollama.js +30 -8
  13. package/dist/clients/openai.d.ts +2 -1
  14. package/dist/clients/openai.js +14 -9
  15. package/dist/clients/openaiResponses.d.ts +2 -1
  16. package/dist/clients/openaiResponses.js +16 -9
  17. package/dist/functions.js +24 -3
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +2 -0
  20. package/dist/model.d.ts +33 -0
  21. package/dist/model.js +132 -0
  22. package/dist/models.d.ts +5 -26
  23. package/dist/models.js +0 -102
  24. package/dist/smolError.d.ts +6 -0
  25. package/dist/smolError.js +12 -0
  26. package/dist/statelogClient.d.ts +2 -1
  27. package/dist/strategies/baseStrategy.d.ts +10 -0
  28. package/dist/strategies/baseStrategy.js +20 -0
  29. package/dist/strategies/fallbackStrategy.d.ts +10 -0
  30. package/dist/strategies/fallbackStrategy.js +48 -0
  31. package/dist/strategies/idStrategy.d.ts +10 -0
  32. package/dist/strategies/idStrategy.js +22 -0
  33. package/dist/strategies/index.d.ts +11 -0
  34. package/dist/strategies/index.js +40 -0
  35. package/dist/strategies/raceStrategy.d.ts +9 -0
  36. package/dist/strategies/raceStrategy.js +37 -0
  37. package/dist/strategies/types.d.ts +31 -0
  38. package/dist/strategies/types.js +1 -0
  39. package/dist/types.d.ts +27 -1
  40. package/package.json +1 -1
package/dist/models.js CHANGED
@@ -1,5 +1,3 @@
1
- import { SmolError } from "./smolError.js";
2
- import { round } from "./util.js";
3
1
  export const speechToTextModels = [
4
2
  { type: "speech-to-text", modelName: "whisper-local", provider: "local" },
5
3
  {
@@ -485,103 +483,3 @@ export function isSpeechToTextModel(model) {
485
483
  export function isEmbeddingsModel(model) {
486
484
  return model.type === "embeddings";
487
485
  }
488
- export function isModelConfig(model) {
489
- return typeof model === "object" && "optimizeFor" in model;
490
- }
491
- const WEIGHTS = {
492
- 1: [1],
493
- 2: [0.6, 0.4],
494
- 3: [0.5, 0.3, 0.2],
495
- 4: [0.4, 0.3, 0.2, 0.1],
496
- };
497
- function getRawMetric(model, optimization) {
498
- const m = model;
499
- switch (optimization) {
500
- case "cost":
501
- return (m.inputTokenCost ?? 0) + (m.outputTokenCost ?? 0);
502
- case "speed":
503
- return m.outputTokensPerSecond ?? 0;
504
- case "accuracy":
505
- return (m.inputTokenCost ?? 0) + (m.outputTokenCost ?? 0);
506
- case "large-context":
507
- return m.maxInputTokens;
508
- }
509
- }
510
- function isLowerBetter(optimization) {
511
- return optimization === "cost";
512
- }
513
- export function pickModel(config, models = textModels) {
514
- let candidates = models.filter((m) => config.providers.includes(m.provider) &&
515
- !("disabled" in m && m.disabled));
516
- if (config.limit?.cost !== undefined) {
517
- candidates = candidates.filter((m) => {
518
- const cost = (m.inputTokenCost ?? 0) + (m.outputTokenCost ?? 0);
519
- return cost <= config.limit.cost;
520
- });
521
- }
522
- if (candidates.length === 0) {
523
- throw new SmolError("No models available for providers: " +
524
- config.providers.join(", ") +
525
- ". Check that the providers have non-disabled models.");
526
- }
527
- if (candidates.length === 1) {
528
- return candidates[0].modelName;
529
- }
530
- const optimizations = config.optimizeFor;
531
- const weights = WEIGHTS[optimizations.length] ?? WEIGHTS[4];
532
- const scores = new Map();
533
- for (const c of candidates) {
534
- scores.set(c.modelName, 0);
535
- }
536
- for (let i = 0; i < optimizations.length; i++) {
537
- const opt = optimizations[i];
538
- const weight = weights[i];
539
- const rawValues = candidates.map((c) => getRawMetric(c, opt));
540
- const min = Math.min(...rawValues);
541
- const max = Math.max(...rawValues);
542
- const range = max - min;
543
- for (let j = 0; j < candidates.length; j++) {
544
- const raw = rawValues[j];
545
- let normalized;
546
- if (range === 0) {
547
- normalized = 0;
548
- }
549
- else if (isLowerBetter(opt)) {
550
- normalized = (raw - min) / range;
551
- }
552
- else {
553
- normalized = (max - raw) / range;
554
- }
555
- scores.set(candidates[j].modelName, scores.get(candidates[j].modelName) + weight * normalized);
556
- }
557
- }
558
- let bestModel = candidates[0];
559
- let bestScore = scores.get(candidates[0].modelName);
560
- for (let i = 1; i < candidates.length; i++) {
561
- const score = scores.get(candidates[i].modelName);
562
- if (score < bestScore) {
563
- bestScore = score;
564
- bestModel = candidates[i];
565
- }
566
- }
567
- return bestModel.modelName;
568
- }
569
- export function calculateCost(modelName, usage) {
570
- const model = getModel(modelName);
571
- if (!model || !isTextModel(model)) {
572
- return null;
573
- }
574
- const inputCost = round((usage.inputTokens * (model.inputTokenCost || 0)) / 1_000_000, 2);
575
- const outputCost = round((usage.outputTokens * (model.outputTokenCost || 0)) / 1_000_000, 2);
576
- const cachedInputCost = usage.cachedInputTokens && model.cachedInputTokenCost
577
- ? round((usage.cachedInputTokens * model.cachedInputTokenCost) / 1_000_000, 2)
578
- : undefined;
579
- const totalCost = round(inputCost + outputCost + (cachedInputCost || 0), 2);
580
- return {
581
- inputCost,
582
- outputCost,
583
- cachedInputCost,
584
- totalCost,
585
- currency: "USD",
586
- };
587
- }
@@ -1,3 +1,9 @@
1
1
  export declare class SmolError extends Error {
2
2
  constructor(message: string);
3
3
  }
4
+ export declare class SmolStructuredOutputError extends SmolError {
5
+ constructor(message: string);
6
+ }
7
+ export declare class SmolTimeoutError extends SmolError {
8
+ constructor(message: string);
9
+ }
package/dist/smolError.js CHANGED
@@ -4,3 +4,15 @@ export class SmolError extends Error {
4
4
  this.name = "SmolTalkError";
5
5
  }
6
6
  }
7
+ export class SmolStructuredOutputError extends SmolError {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "SmolStructuredOutputError";
11
+ }
12
+ }
13
+ export class SmolTimeoutError extends SmolError {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "SmolTimeoutError";
17
+ }
18
+ }
@@ -1,5 +1,6 @@
1
1
  import { Result } from "./types/result.js";
2
- import { ModelConfig, ModelName } from "./models.js";
2
+ import { ModelName } from "./models.js";
3
+ import { ModelConfig } from "./model.js";
3
4
  export type AgencyFile = {
4
5
  name: string;
5
6
  contents: string;
@@ -0,0 +1,10 @@
1
+ import { PromptResult, Result, SmolPromptConfig } from "../types.js";
2
+ import { Strategy, StrategyJSON } from "./types.js";
3
+ export declare class BaseStrategy implements Strategy {
4
+ text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
5
+ textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
6
+ textStream(config: SmolPromptConfig): Promise<Result<AsyncIterable<PromptResult>>>;
7
+ _text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
8
+ _textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
9
+ toJSON(): StrategyJSON;
10
+ }
@@ -0,0 +1,20 @@
1
+ export class BaseStrategy {
2
+ async text(config) {
3
+ return this._text({ ...config, strategy: undefined });
4
+ }
5
+ async textSync(config) {
6
+ return this._textSync({ ...config, strategy: undefined });
7
+ }
8
+ async textStream(config) {
9
+ throw new Error("textStream method not implemented.");
10
+ }
11
+ async _text(config) {
12
+ throw new Error("_text method not implemented.");
13
+ }
14
+ async _textSync(config) {
15
+ throw new Error("_textSync method not implemented.");
16
+ }
17
+ toJSON() {
18
+ throw new Error("toJSON method not implemented.");
19
+ }
20
+ }
@@ -0,0 +1,10 @@
1
+ import { SmolPromptConfig } from "../types.js";
2
+ import { BaseStrategy } from "./baseStrategy.js";
3
+ import { FallbackStrategyConfig, Strategy, StrategyJSON } from "./types.js";
4
+ export declare class FallbackStrategy extends BaseStrategy {
5
+ strategies: Strategy[];
6
+ config: FallbackStrategyConfig;
7
+ constructor(strategies: Strategy[], config: FallbackStrategyConfig);
8
+ _text(config: SmolPromptConfig): Promise<import("../types.js").Result<import("../types.js").PromptResult>>;
9
+ toJSON(): StrategyJSON;
10
+ }
@@ -0,0 +1,48 @@
1
+ import { SmolStructuredOutputError, SmolTimeoutError } from "../smolError.js";
2
+ import { BaseStrategy } from "./baseStrategy.js";
3
+ export class FallbackStrategy extends BaseStrategy {
4
+ strategies;
5
+ config;
6
+ constructor(strategies, config) {
7
+ super();
8
+ this.strategies = strategies;
9
+ this.config = config;
10
+ }
11
+ async _text(config) {
12
+ for (let i = 0; i < this.strategies.length; i++) {
13
+ const strategy = this.strategies[i];
14
+ try {
15
+ const result = await strategy.text(config);
16
+ return result;
17
+ }
18
+ catch (error) {
19
+ if (error instanceof SmolTimeoutError) {
20
+ if (this.config.fallbackOn.includes("timeout")) {
21
+ continue;
22
+ }
23
+ }
24
+ else if (error instanceof SmolStructuredOutputError) {
25
+ if (this.config.fallbackOn.includes("structuredOutputFailure")) {
26
+ continue;
27
+ }
28
+ }
29
+ else {
30
+ if (this.config.fallbackOn.includes("error")) {
31
+ continue;
32
+ }
33
+ }
34
+ throw error;
35
+ }
36
+ }
37
+ throw new Error(`All fallback strategies failed.`);
38
+ }
39
+ toJSON() {
40
+ return {
41
+ type: "fallback",
42
+ params: {
43
+ strategies: this.strategies.map((s) => s.toJSON()),
44
+ config: this.config,
45
+ },
46
+ };
47
+ }
48
+ }
@@ -0,0 +1,10 @@
1
+ import { Model } from "../model.js";
2
+ import { SmolPromptConfig } from "../types.js";
3
+ import { BaseStrategy } from "./baseStrategy.js";
4
+ import { StrategyJSON } from "./types.js";
5
+ export declare class IDStrategy extends BaseStrategy {
6
+ model: Model;
7
+ constructor(model: Model);
8
+ _text(_config: SmolPromptConfig): Promise<import("../types.js").Result<import("../types.js").PromptResult>>;
9
+ toJSON(): StrategyJSON;
10
+ }
@@ -0,0 +1,22 @@
1
+ import { text } from "../functions.js";
2
+ import { BaseStrategy } from "./baseStrategy.js";
3
+ export class IDStrategy extends BaseStrategy {
4
+ model;
5
+ constructor(model) {
6
+ super();
7
+ this.model = model;
8
+ }
9
+ async _text(_config) {
10
+ const config = {
11
+ ..._config,
12
+ model: this.model.getResolvedModel(),
13
+ };
14
+ if (config.hooks?.onStrategyStart) {
15
+ config.hooks.onStrategyStart(config);
16
+ }
17
+ return text({ ...config, stream: false });
18
+ }
19
+ toJSON() {
20
+ return { type: "id", params: { model: this.model.getResolvedModel() } };
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ import { ModelLike } from "../types.js";
2
+ import { FallbackStrategyConfig, Strategy, StrategyJSON } from "./types.js";
3
+ export * from "./baseStrategy.js";
4
+ export * from "./fallbackStrategy.js";
5
+ export * from "./idStrategy.js";
6
+ export * from "./raceStrategy.js";
7
+ export * from "./types.js";
8
+ export declare function race(..._strategies: (Strategy | ModelLike)[]): Strategy;
9
+ export declare function id(model: ModelLike): Strategy;
10
+ export declare function fallback(_strategies: (Strategy | ModelLike)[], config: FallbackStrategyConfig): Strategy;
11
+ export declare function fromJSON(json: StrategyJSON): Strategy;
@@ -0,0 +1,40 @@
1
+ import { Model } from "../model.js";
2
+ import { BaseStrategy } from "./baseStrategy.js";
3
+ import { FallbackStrategy } from "./fallbackStrategy.js";
4
+ import { IDStrategy } from "./idStrategy.js";
5
+ import { RaceStrategy } from "./raceStrategy.js";
6
+ export * from "./baseStrategy.js";
7
+ export * from "./fallbackStrategy.js";
8
+ export * from "./idStrategy.js";
9
+ export * from "./raceStrategy.js";
10
+ export * from "./types.js";
11
+ export function race(..._strategies) {
12
+ const strategies = _strategies.map((s) => s instanceof BaseStrategy
13
+ ? s
14
+ : new IDStrategy(Model.create(s)));
15
+ return new RaceStrategy(strategies);
16
+ }
17
+ export function id(model) {
18
+ return new IDStrategy(Model.create(model));
19
+ }
20
+ export function fallback(_strategies, config) {
21
+ const strategies = _strategies.map((s) => s instanceof BaseStrategy
22
+ ? s
23
+ : new IDStrategy(Model.create(s)));
24
+ return new FallbackStrategy(strategies, config);
25
+ }
26
+ export function fromJSON(json) {
27
+ if (typeof json === "string") {
28
+ return id(json);
29
+ }
30
+ switch (json.type) {
31
+ case "id":
32
+ return id(json.params.model);
33
+ case "race":
34
+ return race(...json.params.strategies.map(fromJSON));
35
+ case "fallback":
36
+ return fallback(json.params.strategies.map(fromJSON), json.params.config);
37
+ default:
38
+ throw new Error(`Unknown strategy type: ${json.type}`);
39
+ }
40
+ }
@@ -0,0 +1,9 @@
1
+ import { SmolPromptConfig } from "../types.js";
2
+ import { BaseStrategy } from "./baseStrategy.js";
3
+ import { Strategy, StrategyJSON } from "./types.js";
4
+ export declare class RaceStrategy extends BaseStrategy {
5
+ strategies: Strategy[];
6
+ constructor(strategies: Strategy[]);
7
+ _text(config: SmolPromptConfig): Promise<import("../types.js").Failure | import("../types.js").Success<import("../types.js").PromptResult>>;
8
+ toJSON(): StrategyJSON;
9
+ }
@@ -0,0 +1,37 @@
1
+ import { BaseStrategy } from "./baseStrategy.js";
2
+ export class RaceStrategy extends BaseStrategy {
3
+ strategies;
4
+ constructor(strategies) {
5
+ super();
6
+ this.strategies = strategies;
7
+ }
8
+ async _text(config) {
9
+ const controllers = this.strategies.map(() => new AbortController());
10
+ // Link to any existing abort signal so external cancellation still works
11
+ if (config.abortSignal) {
12
+ const external = config.abortSignal;
13
+ for (const controller of controllers) {
14
+ external.addEventListener("abort", () => controller.abort(external.reason), { once: true });
15
+ }
16
+ }
17
+ const promises = this.strategies.map((strategy, i) => {
18
+ return strategy.text({
19
+ ...config,
20
+ abortSignal: controllers[i].signal,
21
+ });
22
+ });
23
+ return Promise.race(promises.map((p, i) => p.then((result) => {
24
+ for (let j = 0; j < controllers.length; j++) {
25
+ if (j !== i)
26
+ controllers[j].abort();
27
+ }
28
+ return result;
29
+ })));
30
+ }
31
+ toJSON() {
32
+ return {
33
+ type: "race",
34
+ params: { strategies: this.strategies.map((s) => s.toJSON()) },
35
+ };
36
+ }
37
+ }
@@ -0,0 +1,31 @@
1
+ import { SmolPromptConfig, Result, PromptResult } from "../types.js";
2
+ export interface Strategy {
3
+ text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
4
+ _text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
5
+ textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
6
+ _textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
7
+ textStream(config: SmolPromptConfig): Promise<Result<AsyncIterable<PromptResult>>>;
8
+ toJSON(): StrategyJSON;
9
+ }
10
+ type FallbackReason = "error" | "timeout" | "structuredOutputFailure";
11
+ export type FallbackStrategyConfig = {
12
+ fallbackOn: FallbackReason[];
13
+ };
14
+ export type StrategyJSON = string | {
15
+ type: "id";
16
+ params: {
17
+ model: string;
18
+ };
19
+ } | {
20
+ type: "race";
21
+ params: {
22
+ strategies: StrategyJSON[];
23
+ };
24
+ } | {
25
+ type: "fallback";
26
+ params: {
27
+ strategies: StrategyJSON[];
28
+ config: FallbackStrategyConfig;
29
+ };
30
+ };
31
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/dist/types.d.ts CHANGED
@@ -3,12 +3,23 @@ import { LogLevel } from "egonlog";
3
3
  import { ZodType } from "zod";
4
4
  import { Message } from "./classes/message/index.js";
5
5
  import { ToolCall } from "./classes/ToolCall.js";
6
- import { ModelConfig, ModelName, Provider } from "./models.js";
6
+ import { ModelName, Provider } from "./models.js";
7
7
  import { Result } from "./types/result.js";
8
+ import { Model, ModelConfig } from "./model.js";
9
+ import { Strategy, StrategyJSON } from "./strategies/types.js";
8
10
  export type ThinkingBlock = {
9
11
  text: string;
10
12
  signature: string;
11
13
  };
14
+ export type Budget = {
15
+ timeBudgetMs?: number;
16
+ tokenBudget?: number;
17
+ tokensUsed?: number;
18
+ costBudget?: number;
19
+ costUsed?: number;
20
+ requestBudget?: number;
21
+ requestsUsed?: number;
22
+ };
12
23
  export type PromptConfig = {
13
24
  messages: Message[];
14
25
  tools?: {
@@ -27,6 +38,7 @@ export type PromptConfig = {
27
38
  enabled: boolean;
28
39
  budgetTokens?: number;
29
40
  };
41
+ reasoningEffort?: "low" | "medium" | "high";
30
42
  responseFormatOptions?: Partial<{
31
43
  name: string;
32
44
  strict: boolean;
@@ -35,6 +47,16 @@ export type PromptConfig = {
35
47
  }>;
36
48
  rawAttributes?: Record<string, any>;
37
49
  maxMessages?: number;
50
+ budget?: Budget;
51
+ abortSignal?: AbortSignal;
52
+ hooks?: Partial<{
53
+ onStart: (config: PromptConfig) => void;
54
+ onToolCall: (toolCall: ToolCall) => void;
55
+ onEnd: (result: PromptResult) => void;
56
+ onError: (error: Error) => void;
57
+ onStrategyStart: (config: SmolPromptConfig) => void;
58
+ }>;
59
+ strategy?: Strategy | StrategyJSON;
38
60
  };
39
61
  export type SmolConfig = {
40
62
  openAiApiKey?: string;
@@ -100,6 +122,9 @@ export type StreamChunk = {
100
122
  } | {
101
123
  type: "error";
102
124
  error: string;
125
+ } | {
126
+ type: "timeout";
127
+ error: string;
103
128
  };
104
129
  export interface SmolClient {
105
130
  text(promptConfig: PromptConfig): Promise<Result<PromptResult>> | AsyncGenerator<StreamChunk>;
@@ -114,3 +139,4 @@ export type TextPart = {
114
139
  type: "text";
115
140
  text: string;
116
141
  };
142
+ export type ModelLike = ModelName | ModelConfig | Model;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoltalk",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "description": "A common interface for LLM APIs",
5
5
  "homepage": "https://github.com/egonSchiele/smoltalk",
6
6
  "scripts": {