smoltalk 0.0.15 → 0.0.17

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.
@@ -20,6 +20,7 @@ export declare class AssistantMessage extends BaseMessage implements MessageClas
20
20
  rawData?: any;
21
21
  });
22
22
  get content(): string;
23
+ set content(value: string);
23
24
  get role(): "assistant";
24
25
  get name(): string | undefined;
25
26
  get audio(): any | null | undefined;
@@ -25,6 +25,9 @@ export class AssistantMessage extends BaseMessage {
25
25
  ? this._content
26
26
  : JSON.stringify(this._content);
27
27
  }
28
+ set content(value) {
29
+ this._content = value;
30
+ }
28
31
  get role() {
29
32
  return this._role;
30
33
  }
@@ -13,6 +13,7 @@ export declare class DeveloperMessage extends BaseMessage implements MessageClas
13
13
  rawData?: any;
14
14
  });
15
15
  get content(): string;
16
+ set content(value: string);
16
17
  get role(): "developer";
17
18
  get name(): string | undefined;
18
19
  get rawData(): any;
@@ -15,6 +15,9 @@ export class DeveloperMessage extends BaseMessage {
15
15
  ? this._content
16
16
  : JSON.stringify(this._content);
17
17
  }
18
+ set content(value) {
19
+ this._content = value;
20
+ }
18
21
  get role() {
19
22
  return this._role;
20
23
  }
@@ -13,6 +13,7 @@ export declare class SystemMessage extends BaseMessage implements MessageClass {
13
13
  rawData?: any;
14
14
  });
15
15
  get content(): string;
16
+ set content(value: string);
16
17
  get role(): "system";
17
18
  get name(): string | undefined;
18
19
  get rawData(): any;
@@ -15,6 +15,9 @@ export class SystemMessage extends BaseMessage {
15
15
  ? this._content
16
16
  : JSON.stringify(this._content);
17
17
  }
18
+ set content(value) {
19
+ this._content = value;
20
+ }
18
21
  get role() {
19
22
  return this._role;
20
23
  }
@@ -15,6 +15,7 @@ export declare class ToolMessage extends BaseMessage implements MessageClass {
15
15
  name: string;
16
16
  });
17
17
  get content(): string;
18
+ set content(value: string);
18
19
  get role(): "tool";
19
20
  get name(): string;
20
21
  get tool_call_id(): string;
@@ -17,6 +17,9 @@ export class ToolMessage extends BaseMessage {
17
17
  ? this._content
18
18
  : JSON.stringify(this._content);
19
19
  }
20
+ set content(value) {
21
+ this._content = value;
22
+ }
20
23
  get role() {
21
24
  return this._role;
22
25
  }
@@ -12,6 +12,7 @@ export declare class UserMessage extends BaseMessage implements MessageClass {
12
12
  rawData?: any;
13
13
  });
14
14
  get content(): string;
15
+ set content(value: string);
15
16
  get role(): "user";
16
17
  get name(): string | undefined;
17
18
  get rawData(): any;
@@ -13,6 +13,9 @@ export class UserMessage extends BaseMessage {
13
13
  get content() {
14
14
  return this._content;
15
15
  }
16
+ set content(value) {
17
+ this._content = value;
18
+ }
16
19
  get role() {
17
20
  return this._role;
18
21
  }
@@ -1,13 +1,23 @@
1
- import { PromptConfig, PromptResult, Result, SmolClient, SmolConfig } from "../types.js";
1
+ import { PromptConfig, PromptResult, Result, SmolClient, SmolConfig, StreamChunk } from "../types.js";
2
2
  export declare class BaseClient implements SmolClient {
3
3
  protected config: SmolConfig;
4
4
  constructor(config: SmolConfig);
5
- text(promptConfig: PromptConfig): Promise<Result<PromptResult>>;
5
+ text(promptConfig: Omit<PromptConfig, "stream">): Promise<Result<PromptResult>>;
6
+ text(promptConfig: Omit<PromptConfig, "stream"> & {
7
+ stream: false;
8
+ }): Promise<Result<PromptResult>>;
9
+ text(promptConfig: Omit<PromptConfig, "stream"> & {
10
+ stream: true;
11
+ }): AsyncGenerator<StreamChunk>;
12
+ text(promptConfig: PromptConfig): Promise<Result<PromptResult>> | AsyncGenerator<StreamChunk>;
13
+ textSync(promptConfig: PromptConfig): Promise<Result<PromptResult>>;
6
14
  checkForToolLoops(promptConfig: PromptConfig): {
7
15
  continue: boolean;
8
16
  newPromptConfig: PromptConfig;
9
17
  };
10
18
  textWithRetry(promptConfig: PromptConfig, retries: number): Promise<Result<PromptResult>>;
11
- _text(promptConfig: PromptConfig): Promise<Result<PromptResult>>;
12
- prompt(text: string, promptConfig?: PromptConfig): Promise<Result<PromptResult>>;
19
+ _textSync(promptConfig: PromptConfig): Promise<Result<PromptResult>>;
20
+ prompt(text: string, promptConfig?: PromptConfig): Promise<Result<PromptResult>> | AsyncGenerator<StreamChunk>;
21
+ textStream(config: PromptConfig): AsyncGenerator<StreamChunk>;
22
+ _textStream(config: PromptConfig): AsyncGenerator<StreamChunk>;
13
23
  }
@@ -6,7 +6,15 @@ export class BaseClient {
6
6
  constructor(config) {
7
7
  this.config = config || {};
8
8
  }
9
- async text(promptConfig) {
9
+ text(promptConfig) {
10
+ if (promptConfig.stream) {
11
+ return this.textStream(promptConfig);
12
+ }
13
+ else {
14
+ return this.textSync(promptConfig);
15
+ }
16
+ }
17
+ async textSync(promptConfig) {
10
18
  const { continue: shouldContinue, newPromptConfig } = this.checkForToolLoops(promptConfig);
11
19
  if (!shouldContinue) {
12
20
  return { success: true, value: { output: null, toolCalls: [] } };
@@ -52,7 +60,7 @@ export class BaseClient {
52
60
  return { continue: true, newPromptConfig: promptConfig };
53
61
  }
54
62
  async textWithRetry(promptConfig, retries) {
55
- const result = await this._text(promptConfig);
63
+ const result = await this._textSync(promptConfig);
56
64
  if (result.success) {
57
65
  const { output } = result.value;
58
66
  if (output !== null &&
@@ -72,10 +80,10 @@ export class BaseClient {
72
80
  }
73
81
  return result;
74
82
  }
75
- async _text(promptConfig) {
83
+ async _textSync(promptConfig) {
76
84
  throw new Error("Method not implemented.");
77
85
  }
78
- async prompt(text, promptConfig) {
86
+ prompt(text, promptConfig) {
79
87
  const msg = userMessage(text);
80
88
  const newPromptConfig = {
81
89
  ...promptConfig,
@@ -83,6 +91,31 @@ export class BaseClient {
83
91
  ? [...promptConfig.messages, msg]
84
92
  : [msg],
85
93
  };
86
- return await this.text(newPromptConfig);
94
+ return this.text(newPromptConfig);
95
+ }
96
+ async *textStream(config) {
97
+ const { continue: shouldContinue, newPromptConfig } = this.checkForToolLoops(config);
98
+ if (!shouldContinue) {
99
+ yield { type: "done", result: { output: null, toolCalls: [] } };
100
+ return;
101
+ }
102
+ yield* this._textStream(newPromptConfig);
103
+ }
104
+ // default implementation of text stream just calls the non-streaming version and yields the result
105
+ // clients that support streaming can override this to provide a streaming implementation
106
+ async *_textStream(config) {
107
+ const result = await this._textSync(config);
108
+ if (result.success) {
109
+ if (result.value.output) {
110
+ yield { type: "text", text: result.value.output };
111
+ }
112
+ for (const tc of result.value.toolCalls) {
113
+ yield { type: "tool_call", toolCall: tc };
114
+ }
115
+ yield { type: "done", result: result.value };
116
+ }
117
+ else {
118
+ yield { type: "error", error: result.error };
119
+ }
87
120
  }
88
121
  }
@@ -9,5 +9,5 @@ export declare class SmolGoogle extends BaseClient implements SmolClient {
9
9
  constructor(config: SmolGoogleConfig);
10
10
  getClient(): GoogleGenAI;
11
11
  getModel(): string;
12
- _text(config: PromptConfig): Promise<Result<PromptResult>>;
12
+ _textSync(config: PromptConfig): Promise<Result<PromptResult>>;
13
13
  }
@@ -23,7 +23,7 @@ export class SmolGoogle extends BaseClient {
23
23
  getModel() {
24
24
  return this.model;
25
25
  }
26
- async _text(config) {
26
+ async _textSync(config) {
27
27
  const messages = config.messages.map((msg) => msg.toGoogleMessage());
28
28
  const tools = (config.tools || []).map((tool) => {
29
29
  return zodToGoogleTool(tool.name, tool.schema, {
@@ -42,10 +42,9 @@ export class SmolGoogle extends BaseClient {
42
42
  contents: messages,
43
43
  model: this.model,
44
44
  config: genConfig,
45
+ stream: config.stream || false,
46
+ ...(config.rawAttributes || {}),
45
47
  };
46
- if (config.rawAttributes) {
47
- Object.assign(request, config.rawAttributes);
48
- }
49
48
  this.logger.debug("Sending request to Google Gemini:", JSON.stringify(request, null, 2));
50
49
  // Send the prompt as the latest message
51
50
  const result = await this.client.models.generateContent(request);
@@ -10,5 +10,5 @@ export declare class SmolOllama extends BaseClient implements SmolClient {
10
10
  constructor(config: SmolOllamaConfig);
11
11
  getClient(): Ollama;
12
12
  getModel(): string;
13
- text(config: PromptConfig): Promise<Result<PromptResult>>;
13
+ _textSync(config: PromptConfig): Promise<Result<PromptResult>>;
14
14
  }
@@ -30,7 +30,7 @@ export class SmolOllama extends BaseClient {
30
30
  getModel() {
31
31
  return this.model;
32
32
  }
33
- async text(config) {
33
+ async _textSync(config) {
34
34
  const messages = config.messages.map((msg) => msg.toOpenAIMessage());
35
35
  const tools = (config.tools || []).map((tool) => {
36
36
  return zodToGoogleTool(tool.name, tool.schema, {
@@ -1,5 +1,5 @@
1
1
  import OpenAI from "openai";
2
- import { BaseClientConfig, PromptConfig, PromptResult, Result, SmolClient } from "../types.js";
2
+ import { BaseClientConfig, PromptConfig, PromptResult, Result, SmolClient, StreamChunk } from "../types.js";
3
3
  import { BaseClient } from "./baseClient.js";
4
4
  export type SmolOpenAiConfig = BaseClientConfig;
5
5
  export declare class SmolOpenAi extends BaseClient implements SmolClient {
@@ -9,5 +9,7 @@ export declare class SmolOpenAi extends BaseClient implements SmolClient {
9
9
  constructor(config: SmolOpenAiConfig);
10
10
  getClient(): OpenAI;
11
11
  getModel(): string;
12
- _text(config: PromptConfig): Promise<Result<PromptResult>>;
12
+ private buildRequest;
13
+ _textSync(config: PromptConfig): Promise<Result<PromptResult>>;
14
+ _textStream(config: PromptConfig): AsyncGenerator<StreamChunk>;
13
15
  }
@@ -24,7 +24,7 @@ export class SmolOpenAi extends BaseClient {
24
24
  getModel() {
25
25
  return this.model;
26
26
  }
27
- async _text(config) {
27
+ buildRequest(config) {
28
28
  const messages = config.messages.map((msg) => msg.toOpenAIMessage());
29
29
  const request = {
30
30
  model: this.model,
@@ -34,6 +34,7 @@ export class SmolOpenAi extends BaseClient {
34
34
  description: tool.description,
35
35
  });
36
36
  }),
37
+ ...(config.rawAttributes || {}),
37
38
  };
38
39
  if (config.responseFormat) {
39
40
  request.response_format = {
@@ -44,8 +45,15 @@ export class SmolOpenAi extends BaseClient {
44
45
  },
45
46
  };
46
47
  }
48
+ return request;
49
+ }
50
+ async _textSync(config) {
51
+ const request = this.buildRequest(config);
47
52
  this.logger.debug("Sending request to OpenAI:", JSON.stringify(request, null, 2));
48
- const completion = await this.client.chat.completions.create(request);
53
+ const completion = await this.client.chat.completions.create({
54
+ ...request,
55
+ stream: false,
56
+ });
49
57
  this.logger.debug("Response from OpenAI:", JSON.stringify(completion, null, 2));
50
58
  const message = completion.choices[0].message;
51
59
  const output = message.content;
@@ -63,4 +71,52 @@ export class SmolOpenAi extends BaseClient {
63
71
  }
64
72
  return success({ output, toolCalls });
65
73
  }
74
+ async *_textStream(config) {
75
+ const request = this.buildRequest(config);
76
+ this.logger.debug("Sending streaming request to OpenAI:", JSON.stringify(request, null, 2));
77
+ const completion = await this.client.chat.completions.create({
78
+ ...request,
79
+ stream: true,
80
+ });
81
+ let content = "";
82
+ const toolCallsMap = new Map();
83
+ for await (const chunk of completion) {
84
+ const delta = chunk.choices[0]?.delta;
85
+ if (!delta)
86
+ continue;
87
+ if (delta.content) {
88
+ content += delta.content;
89
+ yield { type: "text", text: delta.content };
90
+ }
91
+ if (delta.tool_calls) {
92
+ for (const tc of delta.tool_calls) {
93
+ const index = tc.index;
94
+ if (!toolCallsMap.has(index)) {
95
+ toolCallsMap.set(index, {
96
+ id: tc.id || "",
97
+ name: tc.function?.name || "",
98
+ arguments: tc.function?.arguments || "",
99
+ });
100
+ }
101
+ else {
102
+ const existing = toolCallsMap.get(index);
103
+ if (tc.id)
104
+ existing.id = tc.id;
105
+ if (tc.function?.name)
106
+ existing.name = tc.function.name;
107
+ if (tc.function?.arguments)
108
+ existing.arguments += tc.function.arguments;
109
+ }
110
+ }
111
+ }
112
+ }
113
+ this.logger.debug("Streaming response completed from OpenAI");
114
+ const toolCalls = [];
115
+ for (const tc of toolCallsMap.values()) {
116
+ const toolCall = new ToolCall(tc.id, tc.name, tc.arguments);
117
+ toolCalls.push(toolCall);
118
+ yield { type: "tool_call", toolCall };
119
+ }
120
+ yield { type: "done", result: { output: content || null, toolCalls } };
121
+ }
66
122
  }
package/dist/types.d.ts CHANGED
@@ -18,6 +18,7 @@ export type PromptConfig = {
18
18
  numSuggestions?: number;
19
19
  parallelToolCalls?: boolean;
20
20
  responseFormat?: ZodType;
21
+ stream?: boolean;
21
22
  responseFormatOptions?: Partial<{
22
23
  name: string;
23
24
  strict: boolean;
@@ -46,10 +47,26 @@ export type PromptResult = {
46
47
  output: string | null;
47
48
  toolCalls: ToolCall[];
48
49
  };
50
+ export type StreamChunk = {
51
+ type: "text";
52
+ text: string;
53
+ } | {
54
+ type: "tool_call";
55
+ toolCall: ToolCall;
56
+ } | {
57
+ type: "done";
58
+ result: PromptResult;
59
+ } | {
60
+ type: "error";
61
+ error: string;
62
+ };
49
63
  export interface SmolClient {
50
- text(config: PromptConfig): Promise<Result<PromptResult>>;
51
- prompt(text: string, config?: PromptConfig): Promise<Result<PromptResult>>;
52
- _text(config: PromptConfig): Promise<Result<PromptResult>>;
64
+ text(promptConfig: PromptConfig): Promise<Result<PromptResult>> | AsyncGenerator<StreamChunk>;
65
+ textSync(config: PromptConfig): Promise<Result<PromptResult>>;
66
+ _textSync(config: PromptConfig): Promise<Result<PromptResult>>;
67
+ textStream(config: PromptConfig): AsyncGenerator<StreamChunk>;
68
+ _textStream(config: PromptConfig): AsyncGenerator<StreamChunk>;
69
+ prompt(text: string, config?: PromptConfig): Promise<Result<PromptResult>> | AsyncGenerator<StreamChunk>;
53
70
  }
54
71
  export type TextPart = {
55
72
  type: "text";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoltalk",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "A common interface for LLM APIs",
5
5
  "homepage": "https://github.com/egonSchiele/smoltalk",
6
6
  "scripts": {
@@ -9,7 +9,8 @@
9
9
  "coverage": "vitest --coverage",
10
10
  "build": "rm -rf dist && tsc",
11
11
  "start": "cd dist && node index.js",
12
- "doc": "typedoc --disableSources --out docs lib && prettier docs/ --write"
12
+ "doc": "typedoc --disableSources --out docs lib && prettier docs/ --write",
13
+ "typecheck": "tsc --noEmit"
13
14
  },
14
15
  "files": [
15
16
  "./dist"