smoltalk 0.0.45 → 0.0.47

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.
@@ -84,14 +84,16 @@ export class SmolAnthropic extends BaseClient {
84
84
  }
85
85
  async _textSync(config) {
86
86
  const { system, messages, tools, thinking } = this.buildRequest(config);
87
- this.logger.debug("Sending request to Anthropic:", {
87
+ let debugData = {
88
88
  model: this.getModel(),
89
89
  max_tokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
90
90
  messages,
91
91
  system,
92
92
  tools,
93
93
  thinking,
94
- });
94
+ };
95
+ this.logger.debug("Sending request to Anthropic:", debugData);
96
+ this.statelogClient?.promptRequest(debugData);
95
97
  const signal = this.getAbortSignal(config);
96
98
  const response = await this.client.messages.create({
97
99
  model: this.getModel(),
@@ -107,6 +109,7 @@ export class SmolAnthropic extends BaseClient {
107
109
  stream: false,
108
110
  }, { ...(signal && { signal }) });
109
111
  this.logger.debug("Response from Anthropic:", response);
112
+ this.statelogClient?.promptResponse(response);
110
113
  let output = null;
111
114
  const toolCalls = [];
112
115
  const thinkingBlocks = [];
@@ -134,14 +137,16 @@ export class SmolAnthropic extends BaseClient {
134
137
  }
135
138
  async *_textStream(config) {
136
139
  const { system, messages, tools, thinking } = this.buildRequest(config);
137
- this.logger.debug("Sending streaming request to Anthropic:", {
140
+ const streamDebugData = {
138
141
  model: this.model,
139
142
  max_tokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
140
143
  messages,
141
144
  system,
142
145
  tools,
143
146
  thinking,
144
- });
147
+ };
148
+ this.logger.debug("Sending streaming request to Anthropic:", streamDebugData);
149
+ this.statelogClient?.promptRequest(streamDebugData);
145
150
  const signal = this.getAbortSignal(config);
146
151
  const stream = await this.client.messages.create({
147
152
  model: this.model,
@@ -219,6 +224,7 @@ export class SmolAnthropic extends BaseClient {
219
224
  }
220
225
  }
221
226
  this.logger.debug("Streaming response completed from Anthropic");
227
+ this.statelogClient?.promptResponse({ content, usage: { inputTokens, outputTokens } });
222
228
  const toolCalls = [];
223
229
  for (const block of toolBlocks.values()) {
224
230
  const toolCall = new ToolCall(block.id, block.name, block.arguments);
@@ -49,6 +49,10 @@ export class BaseClient {
49
49
  promptConfig.messages.length > promptConfig.maxMessages) {
50
50
  const logger = getLogger();
51
51
  logger.warn(`Message limit exceeded: ${promptConfig.messages.length} messages sent, but maxMessages is set to ${promptConfig.maxMessages}. Aborting request.`);
52
+ this.statelogClient?.debug("Message limit exceeded", {
53
+ messageCount: promptConfig.messages.length,
54
+ maxMessages: promptConfig.maxMessages,
55
+ });
52
56
  return {
53
57
  success: false,
54
58
  error: `Message limit exceeded: ${promptConfig.messages.length} messages exceeds the maxMessages limit of ${promptConfig.maxMessages}`,
@@ -71,6 +75,10 @@ export class BaseClient {
71
75
  // Request budget check
72
76
  if (budget.requestBudget !== undefined &&
73
77
  requestsUsed >= budget.requestBudget) {
78
+ this.statelogClient?.debug("Request budget exhausted", {
79
+ requestsUsed,
80
+ requestBudget: budget.requestBudget,
81
+ });
74
82
  return {
75
83
  config,
76
84
  failure: {
@@ -83,6 +91,10 @@ export class BaseClient {
83
91
  if (budget.tokenBudget !== undefined) {
84
92
  const remaining = budget.tokenBudget - tokensUsed;
85
93
  if (remaining <= 0) {
94
+ this.statelogClient?.debug("Token budget exhausted", {
95
+ tokensUsed,
96
+ tokenBudget: budget.tokenBudget,
97
+ });
86
98
  return {
87
99
  config,
88
100
  failure: {
@@ -97,6 +109,10 @@ export class BaseClient {
97
109
  if (budget.costBudget !== undefined) {
98
110
  const remainingUSD = budget.costBudget - costUsed;
99
111
  if (remainingUSD <= 0) {
112
+ this.statelogClient?.debug("Cost budget exhausted", {
113
+ costUsed,
114
+ costBudget: budget.costBudget,
115
+ });
100
116
  return {
101
117
  config,
102
118
  failure: {
@@ -138,6 +154,10 @@ export class BaseClient {
138
154
  const message = timeBudgetMs
139
155
  ? `Request timed out after ${timeBudgetMs}ms`
140
156
  : "Request was aborted";
157
+ this.statelogClient?.debug("Request aborted or timed out", {
158
+ reason: message,
159
+ timeBudgetMs,
160
+ });
141
161
  return { success: false, error: message };
142
162
  }
143
163
  throw err;
@@ -282,6 +302,10 @@ export class BaseClient {
282
302
  if (err instanceof z.ZodError) {
283
303
  logger.warn("Zod error details:", z.prettifyError(err));
284
304
  }
305
+ this.statelogClient?.debug("Response format validation failed", {
306
+ retriesLeft: retries,
307
+ error: errorMessage,
308
+ });
285
309
  this.statelogClient?.diff({
286
310
  message: "Response format validation failed",
287
311
  itemA: promptConfig.responseFormat,
@@ -353,6 +377,10 @@ export class BaseClient {
353
377
  const message = timeBudgetMs
354
378
  ? `Request timed out after ${timeBudgetMs}ms`
355
379
  : "Request was aborted";
380
+ this.statelogClient?.debug("Streaming request aborted or timed out", {
381
+ reason: message,
382
+ timeBudgetMs,
383
+ });
356
384
  yield { type: "timeout", error: message };
357
385
  }
358
386
  else {
@@ -103,6 +103,7 @@ export class SmolGoogle extends BaseClient {
103
103
  // make two requests instead
104
104
  /*********** TOOL CALL REQUEST ************/
105
105
  this.logger.debug("Detected both tool calls and structured response in call to Google Gemini. Making separate request to Google Gemini for tool calls.");
106
+ this.statelogClient?.debug("Google Gemini: splitting tool calls and structured response into separate requests", {});
106
107
  const toolRequest = {
107
108
  ...request,
108
109
  config: {
@@ -154,9 +155,11 @@ export class SmolGoogle extends BaseClient {
154
155
  }
155
156
  async __textSync(request) {
156
157
  this.logger.debug("Sending request to Google Gemini:", JSON.stringify(request, null, 2));
158
+ this.statelogClient?.promptRequest(request);
157
159
  // Send the prompt as the latest message
158
160
  const result = await this.client.models.generateContent(request);
159
161
  this.logger.debug("Response from Google Gemini:", JSON.stringify(result, null, 2));
162
+ this.statelogClient?.promptResponse(result);
160
163
  const output = result.text || null;
161
164
  const toolCalls = [];
162
165
  const thinkingBlocks = [];
@@ -199,10 +202,12 @@ export class SmolGoogle extends BaseClient {
199
202
  const hasStructuredResponse = !!config.responseFormat;
200
203
  if (hasTools && hasStructuredResponse) {
201
204
  this.logger.debug("Gemini does not support streaming responses with both tool calls and structured response formats. Response format will be ignored.");
205
+ this.statelogClient?.debug("Google Gemini: streaming with tools + structured response not supported, ignoring response format", {});
202
206
  request.config.responseMimeType = undefined;
203
207
  request.config.responseJsonSchema = undefined;
204
208
  }
205
209
  this.logger.debug("Sending streaming request to Google Gemini:", JSON.stringify(request, null, 2));
210
+ this.statelogClient?.promptRequest(request);
206
211
  const stream = await this.client.models.generateContentStream(request);
207
212
  let content = "";
208
213
  const toolCallsMap = new Map();
@@ -251,6 +256,7 @@ export class SmolGoogle extends BaseClient {
251
256
  }
252
257
  }
253
258
  this.logger.debug("Streaming response completed from Google Gemini");
259
+ this.statelogClient?.promptResponse({ content, usage, cost });
254
260
  // Yield tool calls
255
261
  const toolCalls = [];
256
262
  for (const tc of toolCallsMap.values()) {
@@ -70,6 +70,7 @@ export class SmolOllama extends BaseClient {
70
70
  Object.assign(request, config.rawAttributes);
71
71
  }
72
72
  this.logger.debug("Sending request to Ollama:", JSON.stringify(request, null, 2));
73
+ this.statelogClient?.promptRequest(request);
73
74
  const signal = this.getAbortSignal(config);
74
75
  const abortHandler = signal ? () => this.client.abort() : undefined;
75
76
  if (signal && abortHandler) {
@@ -81,6 +82,7 @@ export class SmolOllama extends BaseClient {
81
82
  signal.removeEventListener("abort", abortHandler);
82
83
  }
83
84
  this.logger.debug("Response from Ollama:", JSON.stringify(result, null, 2));
85
+ this.statelogClient?.promptResponse(result);
84
86
  const output = result.message?.content || null;
85
87
  const toolCalls = [];
86
88
  if (result.message?.tool_calls) {
@@ -116,6 +118,7 @@ export class SmolOllama extends BaseClient {
116
118
  Object.assign(request, config.rawAttributes);
117
119
  }
118
120
  this.logger.debug("Sending streaming request to Ollama:", JSON.stringify(request, null, 2));
121
+ this.statelogClient?.promptRequest(request);
119
122
  const signal = this.getAbortSignal(config);
120
123
  const abortHandler = signal ? () => this.client.abort() : undefined;
121
124
  if (signal && abortHandler) {
@@ -171,6 +174,7 @@ export class SmolOllama extends BaseClient {
171
174
  usage = usageAndCost.usage;
172
175
  cost = usageAndCost.cost;
173
176
  }
177
+ this.statelogClient?.promptResponse({ content, usage, cost });
174
178
  // Yield tool calls
175
179
  const toolCalls = [];
176
180
  for (const tc of toolCallsMap.values()) {
@@ -71,12 +71,14 @@ export class SmolOpenAi extends BaseClient {
71
71
  async _textSync(config) {
72
72
  const request = this.buildRequest(config);
73
73
  this.logger.debug("Sending request to OpenAI:", JSON.stringify(request, null, 2));
74
+ this.statelogClient?.promptRequest(request);
74
75
  const signal = this.getAbortSignal(config);
75
76
  const completion = await this.client.chat.completions.create({
76
77
  ...request,
77
78
  stream: false,
78
79
  }, { ...(signal && { signal }) });
79
80
  this.logger.debug("Response from OpenAI:", JSON.stringify(completion, null, 2));
81
+ this.statelogClient?.promptResponse(completion);
80
82
  const message = completion.choices[0].message;
81
83
  const output = message.content;
82
84
  const _toolCalls = message.tool_calls;
@@ -88,6 +90,7 @@ export class SmolOpenAi extends BaseClient {
88
90
  }
89
91
  else {
90
92
  this.logger.warn(`Unsupported tool call type: ${tc.type} for tool call ID: ${tc.id}`);
93
+ this.statelogClient?.debug(`Unsupported tool call type: ${tc.type}`, { toolCallId: tc.id });
91
94
  }
92
95
  }
93
96
  }
@@ -104,6 +107,7 @@ export class SmolOpenAi extends BaseClient {
104
107
  async *_textStream(config) {
105
108
  const request = this.buildRequest(config);
106
109
  this.logger.debug("Sending streaming request to OpenAI:", JSON.stringify(request, null, 2));
110
+ this.statelogClient?.promptRequest(request);
107
111
  const signal = this.getAbortSignal(config);
108
112
  const completion = await this.client.chat.completions.create({
109
113
  ...request,
@@ -151,6 +155,7 @@ export class SmolOpenAi extends BaseClient {
151
155
  }
152
156
  }
153
157
  this.logger.debug("Streaming response completed from OpenAI");
158
+ this.statelogClient?.promptResponse({ content, usage, cost });
154
159
  const toolCalls = [];
155
160
  for (const tc of toolCallsMap.values()) {
156
161
  const toolCall = new ToolCall(tc.id, tc.name, tc.arguments);
@@ -105,12 +105,14 @@ export class SmolOpenAiResponses extends BaseClient {
105
105
  async _textSync(config) {
106
106
  const request = this.buildRequest(config);
107
107
  this.logger.debug("Sending request to OpenAI Responses API:", JSON.stringify(request, null, 2));
108
+ this.statelogClient?.promptRequest(request);
108
109
  const signal = this.getAbortSignal(config);
109
110
  const response = await this.client.responses.create({
110
111
  ...request,
111
112
  stream: false,
112
113
  }, { ...(signal && { signal }) });
113
114
  this.logger.debug("Response from OpenAI Responses API:", JSON.stringify(response, null, 2));
115
+ this.statelogClient?.promptResponse(response);
114
116
  const output = response.output_text || null;
115
117
  const toolCalls = [];
116
118
  for (const item of response.output) {
@@ -130,6 +132,7 @@ export class SmolOpenAiResponses extends BaseClient {
130
132
  async *_textStream(config) {
131
133
  const request = this.buildRequest(config);
132
134
  this.logger.debug("Sending streaming request to OpenAI Responses API:", JSON.stringify(request, null, 2));
135
+ this.statelogClient?.promptRequest(request);
133
136
  const signal = this.getAbortSignal(config);
134
137
  const stream = this.client.responses.stream(request, {
135
138
  ...(signal && { signal }),
@@ -200,6 +203,7 @@ export class SmolOpenAiResponses extends BaseClient {
200
203
  }
201
204
  }
202
205
  this.logger.debug("Streaming response completed from OpenAI Responses API");
206
+ this.statelogClient?.promptResponse({ content, usage, cost });
203
207
  const toolCalls = [];
204
208
  for (const fc of functionCalls.values()) {
205
209
  const toolCall = new ToolCall(fc.call_id, fc.name, fc.arguments);
@@ -57,6 +57,8 @@ export declare class StatelogClient {
57
57
  isConditionalEdge: boolean;
58
58
  data: any;
59
59
  }): Promise<void>;
60
+ promptRequest(data: Record<string, any>): Promise<void>;
61
+ promptResponse(data: Record<string, any>): Promise<void>;
60
62
  promptCompletion({ messages, completion, model, timeTaken, tools, responseFormat, }: {
61
63
  messages: any[];
62
64
  completion: any;
@@ -88,6 +88,18 @@ export class StatelogClient {
88
88
  data,
89
89
  });
90
90
  }
91
+ async promptRequest(data) {
92
+ await this.post({
93
+ type: "promptRequest",
94
+ data,
95
+ });
96
+ }
97
+ async promptResponse(data) {
98
+ await this.post({
99
+ type: "promptResponse",
100
+ data,
101
+ });
102
+ }
91
103
  async promptCompletion({ messages, completion, model, timeTaken, tools, responseFormat, }) {
92
104
  await this.post({
93
105
  type: "promptCompletion",
@@ -286,7 +298,7 @@ export function getStatelogClient(config) {
286
298
  const statelogConfig = {
287
299
  host: config.host,
288
300
  traceId: config.traceId || nanoid(),
289
- apiKey: process.env.STATELOG_API_KEY || "",
301
+ apiKey: process.env.STATELOG_SMOLTALK_API_KEY || "",
290
302
  projectId: config.projectId,
291
303
  debugMode: config.debugMode || false,
292
304
  };
@@ -1,6 +1,8 @@
1
+ import { StatelogClient } from "../statelogClient.js";
1
2
  import { PromptResult, Result, SmolPromptConfig } from "../types.js";
2
3
  import { Strategy, StrategyJSON } from "./types.js";
3
4
  export declare class BaseStrategy implements Strategy {
5
+ statelogClient?: StatelogClient;
4
6
  text(config: SmolPromptConfig): Promise<Result<PromptResult>>;
5
7
  textSync(config: SmolPromptConfig): Promise<Result<PromptResult>>;
6
8
  textStream(config: SmolPromptConfig): Promise<Result<AsyncIterable<PromptResult>>>;
@@ -1,11 +1,19 @@
1
+ import { getStatelogClient } from "../statelogClient.js";
1
2
  export class BaseStrategy {
3
+ statelogClient;
2
4
  async text(config) {
5
+ this.statelogClient = config.statelog
6
+ ? getStatelogClient(config.statelog)
7
+ : undefined;
3
8
  if (config.hooks?.onStrategyStart) {
4
9
  config.hooks.onStrategyStart(this, config);
5
10
  }
6
11
  return this._text({ ...config, strategy: undefined });
7
12
  }
8
13
  async textSync(config) {
14
+ this.statelogClient = config.statelog
15
+ ? getStatelogClient(config.statelog)
16
+ : undefined;
9
17
  return this._textSync({ ...config, strategy: undefined });
10
18
  }
11
19
  async textStream(config) {
@@ -30,15 +30,28 @@ export class FallbackStrategy extends BaseStrategy {
30
30
  }
31
31
  if (error instanceof SmolTimeoutError) {
32
32
  if (this.config.fallbackOn.includes("timeout")) {
33
+ this.statelogClient?.debug("FallbackStrategy: falling back due to timeout", {
34
+ failedStrategy: strategy.toString(),
35
+ strategyIndex: i,
36
+ });
33
37
  continue;
34
38
  }
35
39
  }
36
40
  else if (error instanceof SmolStructuredOutputError) {
37
41
  if (this.config.fallbackOn.includes("structuredOutputFailure")) {
42
+ this.statelogClient?.debug("FallbackStrategy: falling back due to structured output failure", {
43
+ failedStrategy: strategy.toString(),
44
+ strategyIndex: i,
45
+ });
38
46
  continue;
39
47
  }
40
48
  }
41
49
  if (this.config.fallbackOn.includes("error")) {
50
+ this.statelogClient?.debug("FallbackStrategy: falling back due to error", {
51
+ failedStrategy: strategy.toString(),
52
+ strategyIndex: i,
53
+ error: error.message,
54
+ });
42
55
  continue;
43
56
  }
44
57
  throw error;
@@ -32,6 +32,10 @@ export class RaceStrategy extends BaseStrategy {
32
32
  if (j !== i) {
33
33
  const logger = getLogger();
34
34
  logger.debug(`RaceStrategy: aborting strategy ${this.strategies[j]} because strategy ${this.strategies[i]} won the race.`);
35
+ this.statelogClient?.debug("RaceStrategy: aborting losing strategy", {
36
+ winner: this.strategies[i].toString(),
37
+ aborted: this.strategies[j].toString(),
38
+ });
35
39
  controllers[j].abort();
36
40
  }
37
41
  }
package/dist/types.d.ts CHANGED
@@ -71,6 +71,7 @@ export type SmolConfig = {
71
71
  statelog?: Partial<{
72
72
  host: string;
73
73
  projectId: string;
74
+ traceId: string;
74
75
  debugMode: boolean;
75
76
  apiKey: string;
76
77
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoltalk",
3
- "version": "0.0.45",
3
+ "version": "0.0.47",
4
4
  "description": "A common interface for LLM APIs",
5
5
  "homepage": "https://github.com/egonSchiele/smoltalk",
6
6
  "scripts": {