smoltalk 0.0.34 → 0.0.35
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,5 +1,5 @@
|
|
|
1
1
|
import { BaseMessage, MessageClass } from "./BaseMessage.js";
|
|
2
|
-
import { TextPart } from "../../types.js";
|
|
2
|
+
import { TextPart, ThinkingBlock } from "../../types.js";
|
|
3
3
|
import { ChatCompletionMessageParam } from "openai/resources";
|
|
4
4
|
import { Content } from "@google/genai";
|
|
5
5
|
import { ToolCall, ToolCallJSON } from "../ToolCall.js";
|
|
@@ -12,6 +12,7 @@ export type AssistantMessageJSON = {
|
|
|
12
12
|
audio: any | null | undefined;
|
|
13
13
|
refusal: string | null | undefined;
|
|
14
14
|
toolCalls: ToolCallJSON[] | undefined;
|
|
15
|
+
thinkingBlocks: ThinkingBlock[] | undefined;
|
|
15
16
|
};
|
|
16
17
|
export declare class AssistantMessage extends BaseMessage implements MessageClass {
|
|
17
18
|
_role: "assistant";
|
|
@@ -20,12 +21,14 @@ export declare class AssistantMessage extends BaseMessage implements MessageClas
|
|
|
20
21
|
_audio?: any | null;
|
|
21
22
|
_refusal?: string | null;
|
|
22
23
|
_toolCalls?: ToolCall[];
|
|
24
|
+
_thinkingBlocks?: ThinkingBlock[];
|
|
23
25
|
_rawData?: any;
|
|
24
26
|
constructor(content: string | Array<TextPart> | null, options?: {
|
|
25
27
|
name?: string;
|
|
26
28
|
audio?: any | null;
|
|
27
29
|
refusal?: string | null;
|
|
28
30
|
toolCalls?: ToolCall[];
|
|
31
|
+
thinkingBlocks?: ThinkingBlock[];
|
|
29
32
|
rawData?: any;
|
|
30
33
|
});
|
|
31
34
|
get content(): string;
|
|
@@ -36,6 +39,7 @@ export declare class AssistantMessage extends BaseMessage implements MessageClas
|
|
|
36
39
|
get refusal(): string | null | undefined;
|
|
37
40
|
get toolCalls(): ToolCall[] | undefined;
|
|
38
41
|
get rawData(): any;
|
|
42
|
+
get thinkingBlocks(): ThinkingBlock[] | undefined;
|
|
39
43
|
toJSON(): AssistantMessageJSON;
|
|
40
44
|
static fromJSON(json: any): AssistantMessage;
|
|
41
45
|
toOpenAIMessage(): ChatCompletionMessageParam;
|
|
@@ -45,6 +49,10 @@ export declare class AssistantMessage extends BaseMessage implements MessageClas
|
|
|
45
49
|
toAnthropicMessage(): {
|
|
46
50
|
role: "assistant";
|
|
47
51
|
content: string | Array<{
|
|
52
|
+
type: "thinking";
|
|
53
|
+
thinking: string;
|
|
54
|
+
signature: string;
|
|
55
|
+
} | {
|
|
48
56
|
type: "text";
|
|
49
57
|
text: string;
|
|
50
58
|
} | {
|
|
@@ -7,6 +7,7 @@ export class AssistantMessage extends BaseMessage {
|
|
|
7
7
|
_audio;
|
|
8
8
|
_refusal;
|
|
9
9
|
_toolCalls;
|
|
10
|
+
_thinkingBlocks;
|
|
10
11
|
_rawData;
|
|
11
12
|
constructor(content, options = {}) {
|
|
12
13
|
super();
|
|
@@ -15,6 +16,7 @@ export class AssistantMessage extends BaseMessage {
|
|
|
15
16
|
this._audio = options.audio;
|
|
16
17
|
this._refusal = options.refusal;
|
|
17
18
|
this._toolCalls = options.toolCalls;
|
|
19
|
+
this._thinkingBlocks = options.thinkingBlocks;
|
|
18
20
|
this._rawData = options.rawData;
|
|
19
21
|
}
|
|
20
22
|
get content() {
|
|
@@ -46,6 +48,9 @@ export class AssistantMessage extends BaseMessage {
|
|
|
46
48
|
get rawData() {
|
|
47
49
|
return this._rawData;
|
|
48
50
|
}
|
|
51
|
+
get thinkingBlocks() {
|
|
52
|
+
return this._thinkingBlocks;
|
|
53
|
+
}
|
|
49
54
|
toJSON() {
|
|
50
55
|
return {
|
|
51
56
|
role: this.role,
|
|
@@ -54,6 +59,7 @@ export class AssistantMessage extends BaseMessage {
|
|
|
54
59
|
audio: this.audio,
|
|
55
60
|
refusal: this.refusal,
|
|
56
61
|
toolCalls: this.toolCalls?.map((tc) => tc.toJSON()),
|
|
62
|
+
thinkingBlocks: this._thinkingBlocks,
|
|
57
63
|
};
|
|
58
64
|
}
|
|
59
65
|
static fromJSON(json) {
|
|
@@ -64,6 +70,7 @@ export class AssistantMessage extends BaseMessage {
|
|
|
64
70
|
toolCalls: json.toolCalls
|
|
65
71
|
? json.toolCalls.map((tcJson) => ToolCall.fromJSON(tcJson))
|
|
66
72
|
: undefined,
|
|
73
|
+
thinkingBlocks: json.thinkingBlocks,
|
|
67
74
|
rawData: json.rawData,
|
|
68
75
|
});
|
|
69
76
|
}
|
|
@@ -95,6 +102,12 @@ export class AssistantMessage extends BaseMessage {
|
|
|
95
102
|
}
|
|
96
103
|
toGoogleMessage() {
|
|
97
104
|
const parts = [];
|
|
105
|
+
// Prepend thought parts with their signatures so Gemini can resume reasoning
|
|
106
|
+
if (this._thinkingBlocks) {
|
|
107
|
+
for (const block of this._thinkingBlocks) {
|
|
108
|
+
parts.push({ thought: true, text: block.text, thoughtSignature: block.signature });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
98
111
|
if (this.content) {
|
|
99
112
|
parts.push({ text: this.content });
|
|
100
113
|
}
|
|
@@ -119,10 +132,18 @@ export class AssistantMessage extends BaseMessage {
|
|
|
119
132
|
? this._content.length > 0
|
|
120
133
|
: this._content.length > 0);
|
|
121
134
|
const hasToolCalls = this._toolCalls && this._toolCalls.length > 0;
|
|
122
|
-
|
|
135
|
+
const hasThinking = this._thinkingBlocks && this._thinkingBlocks.length > 0;
|
|
136
|
+
// If only text and no thinking/tool calls, use string shorthand
|
|
137
|
+
if (!hasToolCalls && !hasThinking) {
|
|
123
138
|
return { role: "assistant", content: this.content };
|
|
124
139
|
}
|
|
125
140
|
const blocks = [];
|
|
141
|
+
// Thinking blocks must come first (Anthropic requires this ordering)
|
|
142
|
+
if (hasThinking) {
|
|
143
|
+
for (const block of this._thinkingBlocks) {
|
|
144
|
+
blocks.push({ type: "thinking", thinking: block.text, signature: block.signature });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
126
147
|
if (hasText) {
|
|
127
148
|
const text = typeof this._content === "string"
|
|
128
149
|
? this._content
|
|
@@ -131,7 +152,7 @@ export class AssistantMessage extends BaseMessage {
|
|
|
131
152
|
blocks.push({ type: "text", text });
|
|
132
153
|
}
|
|
133
154
|
}
|
|
134
|
-
for (const tc of this._toolCalls) {
|
|
155
|
+
for (const tc of this._toolCalls ?? []) {
|
|
135
156
|
blocks.push({ type: "tool_use", id: tc.id, name: tc.name, input: tc.arguments });
|
|
136
157
|
}
|
|
137
158
|
return { role: "assistant", content: blocks };
|
|
@@ -25,6 +25,10 @@ export declare function assistantMessage(content: string | Array<TextPart> | nul
|
|
|
25
25
|
audio?: any | null;
|
|
26
26
|
refusal?: string | null;
|
|
27
27
|
toolCalls?: Array<any>;
|
|
28
|
+
thinkingBlocks?: Array<{
|
|
29
|
+
text: string;
|
|
30
|
+
signature: string;
|
|
31
|
+
}>;
|
|
28
32
|
rawData?: any;
|
|
29
33
|
}): AssistantMessage;
|
|
30
34
|
export declare function developerMessage(content: string | Array<TextPart>, options?: {
|
|
@@ -64,16 +64,20 @@ export class SmolAnthropic extends BaseClient {
|
|
|
64
64
|
description: tool.description,
|
|
65
65
|
}))
|
|
66
66
|
: undefined;
|
|
67
|
-
|
|
67
|
+
const thinking = config.thinking?.enabled
|
|
68
|
+
? { type: "enabled", budget_tokens: config.thinking.budgetTokens ?? 5000 }
|
|
69
|
+
: undefined;
|
|
70
|
+
return { system, messages: anthropicMessages, tools, thinking };
|
|
68
71
|
}
|
|
69
72
|
async _textSync(config) {
|
|
70
|
-
const { system, messages, tools } = this.buildRequest(config);
|
|
73
|
+
const { system, messages, tools, thinking } = this.buildRequest(config);
|
|
71
74
|
this.logger.debug("Sending request to Anthropic:", {
|
|
72
75
|
model: this.model,
|
|
73
76
|
max_tokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
74
77
|
messages,
|
|
75
78
|
system,
|
|
76
79
|
tools,
|
|
80
|
+
thinking,
|
|
77
81
|
});
|
|
78
82
|
const response = await this.client.messages.create({
|
|
79
83
|
model: this.model,
|
|
@@ -81,6 +85,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
81
85
|
messages,
|
|
82
86
|
...(system && { system }),
|
|
83
87
|
...(tools && { tools }),
|
|
88
|
+
...(thinking && { thinking }),
|
|
84
89
|
...(config.temperature !== undefined && {
|
|
85
90
|
temperature: config.temperature,
|
|
86
91
|
}),
|
|
@@ -90,6 +95,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
90
95
|
this.logger.debug("Response from Anthropic:", response);
|
|
91
96
|
let output = null;
|
|
92
97
|
const toolCalls = [];
|
|
98
|
+
const thinkingBlocks = [];
|
|
93
99
|
for (const block of response.content) {
|
|
94
100
|
if (block.type === "text") {
|
|
95
101
|
output = (output ?? "") + block.text;
|
|
@@ -97,24 +103,30 @@ export class SmolAnthropic extends BaseClient {
|
|
|
97
103
|
else if (block.type === "tool_use") {
|
|
98
104
|
toolCalls.push(new ToolCall(block.id, block.name, block.input));
|
|
99
105
|
}
|
|
106
|
+
else if (block.type === "thinking") {
|
|
107
|
+
const b = block;
|
|
108
|
+
thinkingBlocks.push({ text: b.thinking, signature: b.signature });
|
|
109
|
+
}
|
|
100
110
|
}
|
|
101
111
|
const { usage, cost } = this.calculateUsageAndCost(response.usage);
|
|
102
112
|
return success({
|
|
103
113
|
output,
|
|
104
114
|
toolCalls,
|
|
115
|
+
...(thinkingBlocks.length > 0 && { thinkingBlocks }),
|
|
105
116
|
usage,
|
|
106
117
|
cost,
|
|
107
118
|
model: this.model,
|
|
108
119
|
});
|
|
109
120
|
}
|
|
110
121
|
async *_textStream(config) {
|
|
111
|
-
const { system, messages, tools } = this.buildRequest(config);
|
|
122
|
+
const { system, messages, tools, thinking } = this.buildRequest(config);
|
|
112
123
|
this.logger.debug("Sending streaming request to Anthropic:", {
|
|
113
124
|
model: this.model,
|
|
114
125
|
max_tokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
115
126
|
messages,
|
|
116
127
|
system,
|
|
117
128
|
tools,
|
|
129
|
+
thinking,
|
|
118
130
|
});
|
|
119
131
|
const stream = await this.client.messages.create({
|
|
120
132
|
model: this.model,
|
|
@@ -122,6 +134,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
122
134
|
messages,
|
|
123
135
|
...(system && { system }),
|
|
124
136
|
...(tools && { tools }),
|
|
137
|
+
...(thinking && { thinking }),
|
|
125
138
|
...(config.temperature !== undefined && {
|
|
126
139
|
temperature: config.temperature,
|
|
127
140
|
}),
|
|
@@ -131,19 +144,25 @@ export class SmolAnthropic extends BaseClient {
|
|
|
131
144
|
let content = "";
|
|
132
145
|
// Track tool blocks by index: index -> { id, name, arguments (partial JSON) }
|
|
133
146
|
const toolBlocks = new Map();
|
|
147
|
+
// Track thinking blocks by index: index -> { text, signature }
|
|
148
|
+
const thinkingBlockMap = new Map();
|
|
134
149
|
let inputTokens = 0;
|
|
135
150
|
let outputTokens = 0;
|
|
136
151
|
for await (const event of stream) {
|
|
137
152
|
if (event.type === "message_start") {
|
|
138
153
|
inputTokens = event.message.usage.input_tokens;
|
|
139
154
|
}
|
|
140
|
-
else if (event.type === "content_block_start"
|
|
141
|
-
event.content_block.type === "tool_use") {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
155
|
+
else if (event.type === "content_block_start") {
|
|
156
|
+
if (event.content_block.type === "tool_use") {
|
|
157
|
+
toolBlocks.set(event.index, {
|
|
158
|
+
id: event.content_block.id,
|
|
159
|
+
name: event.content_block.name,
|
|
160
|
+
arguments: "",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
else if (event.content_block.type === "thinking") {
|
|
164
|
+
thinkingBlockMap.set(event.index, { text: "", signature: "" });
|
|
165
|
+
}
|
|
147
166
|
}
|
|
148
167
|
else if (event.type === "content_block_delta") {
|
|
149
168
|
if (event.delta.type === "text_delta") {
|
|
@@ -156,6 +175,25 @@ export class SmolAnthropic extends BaseClient {
|
|
|
156
175
|
block.arguments += event.delta.partial_json;
|
|
157
176
|
}
|
|
158
177
|
}
|
|
178
|
+
else if (event.delta.type === "thinking_delta") {
|
|
179
|
+
const block = thinkingBlockMap.get(event.index);
|
|
180
|
+
if (block) {
|
|
181
|
+
block.text += event.delta.thinking;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (event.delta.type === "signature_delta") {
|
|
185
|
+
const block = thinkingBlockMap.get(event.index);
|
|
186
|
+
if (block) {
|
|
187
|
+
block.signature = event.delta.signature;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (event.type === "content_block_stop") {
|
|
192
|
+
// Emit thinking chunk once the block is fully assembled
|
|
193
|
+
const thinkingBlock = thinkingBlockMap.get(event.index);
|
|
194
|
+
if (thinkingBlock) {
|
|
195
|
+
yield { type: "thinking", text: thinkingBlock.text, signature: thinkingBlock.signature };
|
|
196
|
+
}
|
|
159
197
|
}
|
|
160
198
|
else if (event.type === "message_delta") {
|
|
161
199
|
outputTokens = event.usage.output_tokens;
|
|
@@ -168,6 +206,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
168
206
|
toolCalls.push(toolCall);
|
|
169
207
|
yield { type: "tool_call", toolCall };
|
|
170
208
|
}
|
|
209
|
+
const thinkingBlocks = Array.from(thinkingBlockMap.values());
|
|
171
210
|
const usage = {
|
|
172
211
|
inputTokens,
|
|
173
212
|
outputTokens,
|
|
@@ -179,6 +218,7 @@ export class SmolAnthropic extends BaseClient {
|
|
|
179
218
|
result: {
|
|
180
219
|
output: content || null,
|
|
181
220
|
toolCalls,
|
|
221
|
+
...(thinkingBlocks.length > 0 && { thinkingBlocks }),
|
|
182
222
|
usage,
|
|
183
223
|
cost,
|
|
184
224
|
model: this.model,
|
package/dist/clients/google.js
CHANGED
|
@@ -91,6 +91,7 @@ export class SmolGoogle extends BaseClient {
|
|
|
91
91
|
this.logger.debug("Response from Google Gemini:", JSON.stringify(result, null, 2));
|
|
92
92
|
const output = result.text || null;
|
|
93
93
|
const toolCalls = [];
|
|
94
|
+
const thinkingBlocks = [];
|
|
94
95
|
result.candidates?.forEach((candidate) => {
|
|
95
96
|
if (candidate.content && candidate.content.parts) {
|
|
96
97
|
candidate.content.parts.forEach((part) => {
|
|
@@ -98,6 +99,13 @@ export class SmolGoogle extends BaseClient {
|
|
|
98
99
|
const functionCall = part.functionCall;
|
|
99
100
|
toolCalls.push(new ToolCall("", functionCall.name, functionCall.args));
|
|
100
101
|
}
|
|
102
|
+
// Capture thought parts (thought: true indicates a thinking part)
|
|
103
|
+
if (part.thoughtSignature) {
|
|
104
|
+
thinkingBlocks.push({
|
|
105
|
+
text: part.text || "",
|
|
106
|
+
signature: part.thoughtSignature,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
101
109
|
});
|
|
102
110
|
}
|
|
103
111
|
});
|
|
@@ -107,6 +115,7 @@ export class SmolGoogle extends BaseClient {
|
|
|
107
115
|
return success({
|
|
108
116
|
output,
|
|
109
117
|
toolCalls,
|
|
118
|
+
...(thinkingBlocks.length > 0 && { thinkingBlocks }),
|
|
110
119
|
usage,
|
|
111
120
|
cost,
|
|
112
121
|
model: request.model,
|
|
@@ -118,6 +127,7 @@ export class SmolGoogle extends BaseClient {
|
|
|
118
127
|
const stream = await this.client.models.generateContentStream(request);
|
|
119
128
|
let content = "";
|
|
120
129
|
const toolCallsMap = new Map();
|
|
130
|
+
const thinkingBlocks = [];
|
|
121
131
|
let usage;
|
|
122
132
|
let cost;
|
|
123
133
|
for await (const chunk of stream) {
|
|
@@ -127,22 +137,28 @@ export class SmolGoogle extends BaseClient {
|
|
|
127
137
|
usage = usageAndCost.usage;
|
|
128
138
|
cost = usageAndCost.cost;
|
|
129
139
|
}
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
content
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
140
|
+
// Iterate raw parts to capture thought signatures and regular content
|
|
141
|
+
for (const candidate of chunk.candidates || []) {
|
|
142
|
+
for (const part of candidate?.content?.parts || []) {
|
|
143
|
+
const p = part;
|
|
144
|
+
if (p.thoughtSignature) {
|
|
145
|
+
const block = {
|
|
146
|
+
text: p.text || "",
|
|
147
|
+
signature: p.thoughtSignature,
|
|
148
|
+
};
|
|
149
|
+
thinkingBlocks.push(block);
|
|
150
|
+
yield { type: "thinking", text: block.text, signature: block.signature };
|
|
151
|
+
}
|
|
152
|
+
else if (p.text) {
|
|
153
|
+
content += p.text;
|
|
154
|
+
yield { type: "text", text: p.text };
|
|
155
|
+
}
|
|
156
|
+
else if (p.functionCall) {
|
|
157
|
+
const id = p.functionCall.id || p.functionCall.name || "";
|
|
158
|
+
const name = p.functionCall.name || "";
|
|
159
|
+
if (!toolCallsMap.has(id)) {
|
|
160
|
+
toolCallsMap.set(id, { id, name, arguments: p.functionCall.args });
|
|
161
|
+
}
|
|
146
162
|
}
|
|
147
163
|
}
|
|
148
164
|
}
|
|
@@ -160,6 +176,7 @@ export class SmolGoogle extends BaseClient {
|
|
|
160
176
|
result: {
|
|
161
177
|
output: content || null,
|
|
162
178
|
toolCalls,
|
|
179
|
+
...(thinkingBlocks.length > 0 && { thinkingBlocks }),
|
|
163
180
|
usage,
|
|
164
181
|
cost,
|
|
165
182
|
model: request.model,
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ import { Message } from "./classes/message/index.js";
|
|
|
5
5
|
import { ToolCall } from "./classes/ToolCall.js";
|
|
6
6
|
import { ModelConfig, ModelName, Provider } from "./models.js";
|
|
7
7
|
import { Result } from "./types/result.js";
|
|
8
|
+
export type ThinkingBlock = {
|
|
9
|
+
text: string;
|
|
10
|
+
signature: string;
|
|
11
|
+
};
|
|
8
12
|
export type PromptConfig = {
|
|
9
13
|
messages: Message[];
|
|
10
14
|
tools?: {
|
|
@@ -19,6 +23,10 @@ export type PromptConfig = {
|
|
|
19
23
|
parallelToolCalls?: boolean;
|
|
20
24
|
responseFormat?: ZodType;
|
|
21
25
|
stream?: boolean;
|
|
26
|
+
thinking?: {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
budgetTokens?: number;
|
|
29
|
+
};
|
|
22
30
|
responseFormatOptions?: Partial<{
|
|
23
31
|
name: string;
|
|
24
32
|
strict: boolean;
|
|
@@ -65,6 +73,7 @@ export type CostEstimate = {
|
|
|
65
73
|
export type PromptResult = {
|
|
66
74
|
output: string | null;
|
|
67
75
|
toolCalls: ToolCall[];
|
|
76
|
+
thinkingBlocks?: ThinkingBlock[];
|
|
68
77
|
usage?: TokenUsage;
|
|
69
78
|
cost?: CostEstimate;
|
|
70
79
|
model?: ModelName | ModelConfig;
|
|
@@ -72,6 +81,10 @@ export type PromptResult = {
|
|
|
72
81
|
export type StreamChunk = {
|
|
73
82
|
type: "text";
|
|
74
83
|
text: string;
|
|
84
|
+
} | {
|
|
85
|
+
type: "thinking";
|
|
86
|
+
text: string;
|
|
87
|
+
signature?: string;
|
|
75
88
|
} | {
|
|
76
89
|
type: "tool_call";
|
|
77
90
|
toolCall: ToolCall;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smoltalk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"description": "A common interface for LLM APIs",
|
|
5
5
|
"homepage": "https://github.com/egonSchiele/smoltalk",
|
|
6
6
|
"scripts": {
|
|
@@ -47,4 +47,4 @@
|
|
|
47
47
|
"ollama": "^0.6.3",
|
|
48
48
|
"openai": "^6.15.0"
|
|
49
49
|
}
|
|
50
|
-
}
|
|
50
|
+
}
|