smoltalk 0.0.60 → 0.0.62

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.
@@ -1,4 +1,4 @@
1
- import { userMessage, assistantMessage, } from "../classes/message/index.js";
1
+ import { userMessage, assistantMessage } from "../classes/message/index.js";
2
2
  import { latencyTracker } from "../latencyTracker.js";
3
3
  import { getLogger } from "../util/logger.js";
4
4
  import { SmolStructuredOutputError } from "../smolError.js";
@@ -149,6 +149,10 @@ export class BaseClient {
149
149
  return rawValue;
150
150
  }
151
151
  }
152
+ // 1.5 Look for { type: "object", properties: { response: { ... } } } pattern
153
+ if (rawValue.type === "object" && rawValue.properties) {
154
+ return this.extractResponse(promptConfig, rawValue.properties, schema, depth + 1);
155
+ }
152
156
  // 2. String → try JSON.parse, then recurse
153
157
  if (typeof rawValue === "string") {
154
158
  const stripped = rawValue
package/dist/functions.js CHANGED
@@ -1,7 +1,8 @@
1
- import { getClient } from "./client.js";
1
+ import { BaseMessage, messageFromJSON, } from "./classes/message/index.js";
2
2
  import { Model } from "./model.js";
3
3
  import { BaseStrategy } from "./strategies/baseStrategy.js";
4
4
  import { fromJSON } from "./strategies/index.js";
5
+ import { getLogger } from "./util/logger.js";
5
6
  function getStrategy(model) {
6
7
  if (model instanceof BaseStrategy)
7
8
  return model;
@@ -29,16 +30,27 @@ export function splitConfig(config) {
29
30
  promptConfig,
30
31
  };
31
32
  }
33
+ function fixMessagesIfNecessary(messages) {
34
+ if (messages && messages.length > 0) {
35
+ if (!(messages[0] instanceof BaseMessage)) {
36
+ getLogger().warn("Messages are not instances of smoltalk.BaseMessage");
37
+ return messages.map((m) => messageFromJSON(m));
38
+ }
39
+ }
40
+ return messages;
41
+ }
32
42
  export function text(config) {
33
43
  const strategy = getStrategy(config.model);
44
+ config.messages = fixMessagesIfNecessary(config.messages);
34
45
  return strategy.text(config);
35
46
  }
36
47
  export function textSync(config) {
37
48
  const strategy = getStrategy(config.model);
49
+ config.messages = fixMessagesIfNecessary(config.messages);
38
50
  return strategy.textSync(config);
39
51
  }
40
52
  export function textStream(config) {
41
- const { smolConfig, promptConfig } = splitConfig(config);
42
- const client = getClient(smolConfig);
43
- return client.textStream(promptConfig);
53
+ const strategy = getStrategy(config.model);
54
+ config.messages = fixMessagesIfNecessary(config.messages);
55
+ return strategy.textStream(config);
44
56
  }
@@ -1,13 +1,21 @@
1
1
  import { StatelogClient } from "../statelogClient.js";
2
- import { PromptResult, Result, SmolPromptConfig } from "../types.js";
2
+ import { PromptResult, Result, SmolPromptConfig, StreamChunk } from "../types.js";
3
3
  import { Strategy, StrategyJSON } from "./types.js";
4
4
  export declare class BaseStrategy implements Strategy {
5
5
  statelogClient?: StatelogClient;
6
- text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
6
+ text(config: Omit<SmolPromptConfig, "stream">): Promise<Result<PromptResult>>;
7
+ text(config: Omit<SmolPromptConfig, "stream"> & {
8
+ stream: false;
9
+ }): Promise<Result<PromptResult>>;
10
+ text(config: Omit<SmolPromptConfig, "stream"> & {
11
+ stream: true;
12
+ }): AsyncGenerator<StreamChunk>;
13
+ text(config: SmolPromptConfig): Promise<Result<PromptResult>> | AsyncGenerator<StreamChunk>;
7
14
  textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
8
- textStream(config: SmolPromptConfig): Promise<Result<AsyncIterable<PromptResult>>>;
15
+ textStream(config: SmolPromptConfig): AsyncGenerator<StreamChunk>;
9
16
  _text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
10
17
  _textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
18
+ _textStream(config: SmolPromptConfig): AsyncGenerator<StreamChunk>;
11
19
  toJSON(): StrategyJSON;
12
20
  toString(): string;
13
21
  toShortString(): string;
@@ -1,7 +1,7 @@
1
1
  import { getStatelogClient } from "../statelogClient.js";
2
2
  export class BaseStrategy {
3
3
  statelogClient;
4
- async text(config) {
4
+ text(config) {
5
5
  this.statelogClient = config.statelog
6
6
  ? getStatelogClient(config.statelog)
7
7
  : undefined;
@@ -10,6 +10,9 @@ export class BaseStrategy {
10
10
  this.statelogClient?.debug(`Calling onStrategyStart hook for strategy ${this.toString()}`);
11
11
  config.hooks.onStrategyStart(this, config);
12
12
  }
13
+ if (config.stream) {
14
+ return this.textStream(config);
15
+ }
13
16
  return this._text(config);
14
17
  }
15
18
  async textSync(config) {
@@ -19,8 +22,12 @@ export class BaseStrategy {
19
22
  this.statelogClient?.debug(`Starting strategy (sync) ${this.toString()}`);
20
23
  return this._textSync(config);
21
24
  }
22
- async textStream(config) {
23
- throw new Error("textStream method not implemented.");
25
+ textStream(config) {
26
+ this.statelogClient = config.statelog
27
+ ? getStatelogClient(config.statelog)
28
+ : undefined;
29
+ this.statelogClient?.debug(`Starting strategy (stream) ${this.toString()}`);
30
+ return this._textStream(config);
24
31
  }
25
32
  async _text(config) {
26
33
  throw new Error("_text method not implemented.");
@@ -28,6 +35,21 @@ export class BaseStrategy {
28
35
  async _textSync(config) {
29
36
  throw new Error("_textSync method not implemented.");
30
37
  }
38
+ async *_textStream(config) {
39
+ const result = await this._textSync(config);
40
+ if (result.success) {
41
+ if (result.value.output) {
42
+ yield { type: "text", text: result.value.output };
43
+ }
44
+ for (const tc of result.value.toolCalls) {
45
+ yield { type: "tool_call", toolCall: tc };
46
+ }
47
+ yield { type: "done", result: result.value };
48
+ }
49
+ else {
50
+ yield { type: "error", error: result.error };
51
+ }
52
+ }
31
53
  toJSON() {
32
54
  throw new Error("toJSON method not implemented.");
33
55
  }
@@ -1,5 +1,5 @@
1
1
  import { Model } from "../model.js";
2
- import { PromptResult, Result, SmolPromptConfig } from "../types.js";
2
+ import { PromptResult, Result, SmolPromptConfig, StreamChunk } from "../types.js";
3
3
  import { BaseStrategy } from "./baseStrategy.js";
4
4
  import { StrategyJSON } from "./types.js";
5
5
  export declare class FastestStrategy extends BaseStrategy {
@@ -8,7 +8,9 @@ export declare class FastestStrategy extends BaseStrategy {
8
8
  constructor(models: (string | Model)[], epsilon?: number);
9
9
  toString(): string;
10
10
  toShortString(): string;
11
+ private chooseModel;
11
12
  _text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
13
+ _textStream(config: SmolPromptConfig): AsyncGenerator<StreamChunk>;
12
14
  private pickFastest;
13
15
  /** Get tokens/sec for a model: tracked latency first, then static estimate, then 0. */
14
16
  private getSpeed;
@@ -20,47 +20,53 @@ export class FastestStrategy extends BaseStrategy {
20
20
  toShortString() {
21
21
  return `fastest([${this.models.map((s) => s.toString()).join(", ")}])`;
22
22
  }
23
- async _text(config) {
23
+ chooseModel(config) {
24
24
  const resolved = this.models.map((model) => Model.create(model));
25
- let chosen = null;
26
25
  const logger = getLogger(config.logLevel);
27
26
  if (Math.random() < this.epsilon) {
28
27
  // Explore: pick a random model
29
- chosen = resolved[Math.floor(Math.random() * resolved.length)];
28
+ const chosen = resolved[Math.floor(Math.random() * resolved.length)];
30
29
  logger.debug("fastest strategy - exploring random model", {
31
30
  model: chosen.getResolvedModel(),
32
31
  });
33
32
  this.statelogClient?.debug("fastest strategy - picking random model", {
34
33
  model: chosen.getResolvedModel(),
35
34
  });
35
+ return chosen;
36
36
  }
37
- else {
38
- // Exploit: pick the fastest model by tracked latency
39
- chosen = this.pickFastest(resolved);
40
- if (chosen) {
41
- logger.debug("fastest strategy - exploiting fastest model", {
42
- model: chosen.getResolvedModel(),
43
- });
44
- this.statelogClient?.debug("fastest strategy - using fastest model", {
45
- model: chosen.getResolvedModel(),
46
- });
47
- }
48
- else {
49
- // we don't have latency data for any model, so just pick randomly
50
- chosen = resolved[Math.floor(Math.random() * resolved.length)];
51
- logger.debug("fastest strategy - no latency data, picking random model", {
52
- models: resolved.map((m) => m.getResolvedModel()),
53
- chosen: chosen.getResolvedModel(),
54
- });
55
- this.statelogClient?.debug("fastest strategy - no latency data, picking random model", {
56
- models: resolved.map((m) => m.getResolvedModel()),
57
- chosen,
58
- });
59
- }
37
+ // Exploit: pick the fastest model by tracked latency
38
+ const fastest = this.pickFastest(resolved);
39
+ if (fastest) {
40
+ logger.debug("fastest strategy - exploiting fastest model", {
41
+ model: fastest.getResolvedModel(),
42
+ });
43
+ this.statelogClient?.debug("fastest strategy - using fastest model", {
44
+ model: fastest.getResolvedModel(),
45
+ });
46
+ return fastest;
60
47
  }
48
+ // we don't have latency data for any model, so just pick randomly
49
+ const chosen = resolved[Math.floor(Math.random() * resolved.length)];
50
+ logger.debug("fastest strategy - no latency data, picking random model", {
51
+ models: resolved.map((m) => m.getResolvedModel()),
52
+ chosen: chosen.getResolvedModel(),
53
+ });
54
+ this.statelogClient?.debug("fastest strategy - no latency data, picking random model", {
55
+ models: resolved.map((m) => m.getResolvedModel()),
56
+ chosen,
57
+ });
58
+ return chosen;
59
+ }
60
+ async _text(config) {
61
+ const chosen = this.chooseModel(config);
61
62
  const strategy = new IDStrategy(chosen);
62
63
  return strategy.text(config);
63
64
  }
65
+ async *_textStream(config) {
66
+ const chosen = this.chooseModel(config);
67
+ const strategy = new IDStrategy(chosen);
68
+ yield* strategy.textStream(config);
69
+ }
64
70
  pickFastest(models) {
65
71
  let best = null;
66
72
  let bestSpeed = 0;
@@ -1,5 +1,5 @@
1
1
  import { Model } from "../model.js";
2
- import { ModelLike, PromptResult, Result, SmolPromptConfig } from "../types.js";
2
+ import { ModelLike, PromptResult, Result, SmolPromptConfig, StreamChunk } from "../types.js";
3
3
  import { BaseStrategy } from "./baseStrategy.js";
4
4
  import { StrategyJSON } from "./types.js";
5
5
  export declare class IDStrategy extends BaseStrategy {
@@ -7,8 +7,10 @@ export declare class IDStrategy extends BaseStrategy {
7
7
  constructor(model: ModelLike, provider?: string);
8
8
  toString(): string;
9
9
  toShortString(): string;
10
+ private _getClientAndConfig;
10
11
  _text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
11
12
  _textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
13
+ _textStream(config: SmolPromptConfig): AsyncGenerator<StreamChunk>;
12
14
  toJSON(): StrategyJSON;
13
15
  static fromJSON(json: unknown): IDStrategy;
14
16
  }
@@ -15,10 +15,7 @@ export class IDStrategy extends BaseStrategy {
15
15
  toShortString() {
16
16
  return `id(${this.model.toString()})`;
17
17
  }
18
- async _text(config) {
19
- return this._textSync(config);
20
- }
21
- async _textSync(config) {
18
+ _getClientAndConfig(config) {
22
19
  const configOverrides = {
23
20
  model: this.model.getResolvedModel(),
24
21
  provider: this.model.getProvider(),
@@ -31,8 +28,19 @@ export class IDStrategy extends BaseStrategy {
31
28
  ...smolConfig,
32
29
  ...configOverrides,
33
30
  });
31
+ return { client, promptConfig };
32
+ }
33
+ async _text(config) {
34
+ return this._textSync(config);
35
+ }
36
+ async _textSync(config) {
37
+ const { client, promptConfig } = this._getClientAndConfig(config);
34
38
  return client.textSync(promptConfig);
35
39
  }
40
+ async *_textStream(config) {
41
+ const { client, promptConfig } = this._getClientAndConfig(config);
42
+ yield* client.textStream(promptConfig);
43
+ }
36
44
  // todo: this toJSON isn't fully accurate as it resolves the model,
37
45
  // so strategy vs strategy.fromJSON(strategy.toJSON()) won't be the same
38
46
  toJSON() {
@@ -1,4 +1,4 @@
1
- import { ModelParam, SmolPromptConfig } from "../types.js";
1
+ import { ModelParam, PromptResult, SmolPromptConfig } from "../types.js";
2
2
  import { BaseStrategy } from "./baseStrategy.js";
3
3
  import { Strategy, StrategyJSON } from "./types.js";
4
4
  export declare class RaceStrategy extends BaseStrategy {
@@ -6,7 +6,7 @@ export declare class RaceStrategy extends BaseStrategy {
6
6
  constructor(strategies: ModelParam[]);
7
7
  toString(): string;
8
8
  toShortString(): string;
9
- _text(config: SmolPromptConfig): Promise<import("../types.js").Failure | import("../types.js").Success<import("../types.js").PromptResult>>;
9
+ _text(config: SmolPromptConfig): Promise<import("../types.js").Failure | import("../types.js").Success<PromptResult>>;
10
10
  toJSON(): StrategyJSON;
11
11
  static fromJSON(json: unknown): RaceStrategy;
12
12
  }
@@ -1,4 +1,4 @@
1
- import { ModelParam, PromptResult, Result, SmolPromptConfig } from "../types.js";
1
+ import { ModelParam, PromptResult, Result, SmolPromptConfig, StreamChunk } from "../types.js";
2
2
  import { BaseStrategy } from "./baseStrategy.js";
3
3
  import { Strategy, StrategyJSON } from "./types.js";
4
4
  export declare class RandomStrategy extends BaseStrategy {
@@ -7,6 +7,7 @@ export declare class RandomStrategy extends BaseStrategy {
7
7
  toString(): string;
8
8
  toShortString(): string;
9
9
  _text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
10
+ _textStream(config: SmolPromptConfig): AsyncGenerator<StreamChunk>;
10
11
  toJSON(): StrategyJSON;
11
12
  static fromJSON(json: unknown): RandomStrategy;
12
13
  }
@@ -23,6 +23,14 @@ export class RandomStrategy extends BaseStrategy {
23
23
  const result = await strategy.text(config);
24
24
  return result;
25
25
  }
26
+ async *_textStream(config) {
27
+ const randomIndex = Math.floor(Math.random() * this.strategies.length);
28
+ const strategy = this.strategies[randomIndex];
29
+ this.statelogClient?.debug("random strategy chosen (stream)", {
30
+ strategy,
31
+ });
32
+ yield* strategy.textStream(config);
33
+ }
26
34
  toJSON() {
27
35
  return {
28
36
  type: "random",
@@ -1,11 +1,12 @@
1
1
  import { z } from "zod";
2
- import { SmolPromptConfig, Result, PromptResult } from "../types.js";
2
+ import { SmolPromptConfig, Result, PromptResult, StreamChunk } from "../types.js";
3
3
  export interface Strategy {
4
- text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
4
+ text(config: SmolPromptConfig): Promise<Result<PromptResult>> | AsyncGenerator<StreamChunk>;
5
5
  _text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
6
6
  textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
7
7
  _textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
8
- textStream(config: SmolPromptConfig): Promise<Result<AsyncIterable<PromptResult>>>;
8
+ textStream(config: SmolPromptConfig): AsyncGenerator<StreamChunk>;
9
+ _textStream(config: SmolPromptConfig): AsyncGenerator<StreamChunk>;
9
10
  toJSON(): StrategyJSON;
10
11
  toString(): string;
11
12
  toShortString(): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoltalk",
3
- "version": "0.0.60",
3
+ "version": "0.0.62",
4
4
  "description": "A common interface for LLM APIs",
5
5
  "homepage": "https://github.com/egonSchiele/smoltalk",
6
6
  "scripts": {