@purista/harness-openai 1.2.1 → 1.2.3
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.
- package/dist/index.js +53 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -67,33 +67,31 @@ class OpenAiModelProvider extends BaseModelProvider {
|
|
|
67
67
|
req.signal.throwIfAborted();
|
|
68
68
|
const stream = await createChatCompletion(this.client, req, true);
|
|
69
69
|
let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
70
|
+
let finishReason = 'stop';
|
|
71
|
+
const toolState = new Map();
|
|
70
72
|
for await (const chunk of stream) {
|
|
71
73
|
req.signal.throwIfAborted();
|
|
72
|
-
|
|
74
|
+
// The usage chunk arrives with an empty choices array, so read it first.
|
|
75
|
+
if (chunk.usage) {
|
|
76
|
+
usage = toUsage(chunk.usage.prompt_tokens, chunk.usage.completion_tokens);
|
|
77
|
+
}
|
|
78
|
+
const choice = chunk.choices?.[0];
|
|
73
79
|
if (!choice)
|
|
74
80
|
continue;
|
|
75
81
|
if (choice.delta?.content) {
|
|
76
82
|
yield { kind: 'delta', text: choice.delta.content };
|
|
77
83
|
}
|
|
78
84
|
if (choice.delta?.tool_calls) {
|
|
79
|
-
|
|
80
|
-
if (!call.function?.name || !call.id)
|
|
81
|
-
continue;
|
|
82
|
-
yield {
|
|
83
|
-
kind: 'tool_call',
|
|
84
|
-
call: {
|
|
85
|
-
id: call.id,
|
|
86
|
-
name: call.function.name,
|
|
87
|
-
arguments: parseToolArgs(call.function.arguments, req, 'textStream')
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
}
|
|
85
|
+
accumulateToolCallDeltas(toolState, choice.delta.tool_calls);
|
|
91
86
|
}
|
|
92
|
-
if (
|
|
93
|
-
|
|
87
|
+
if (choice.finish_reason) {
|
|
88
|
+
finishReason = toFinishReason(choice.finish_reason);
|
|
94
89
|
}
|
|
95
90
|
}
|
|
96
|
-
|
|
91
|
+
for (const call of finalizeStreamToolCalls(toolState, req, 'textStream')) {
|
|
92
|
+
yield { kind: 'tool_call', call };
|
|
93
|
+
}
|
|
94
|
+
yield { kind: 'finish', usage, finishReason };
|
|
97
95
|
}
|
|
98
96
|
async doObject(req) {
|
|
99
97
|
req.signal.throwIfAborted();
|
|
@@ -112,10 +110,15 @@ class OpenAiModelProvider extends BaseModelProvider {
|
|
|
112
110
|
req.signal.throwIfAborted();
|
|
113
111
|
let partial = '';
|
|
114
112
|
let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
113
|
+
let finishReason = 'stop';
|
|
114
|
+
const toolState = new Map();
|
|
115
115
|
const stream = await createChatCompletion(this.client, req, true);
|
|
116
116
|
for await (const chunk of stream) {
|
|
117
117
|
req.signal.throwIfAborted();
|
|
118
|
-
|
|
118
|
+
if (chunk.usage) {
|
|
119
|
+
usage = toUsage(chunk.usage.prompt_tokens, chunk.usage.completion_tokens);
|
|
120
|
+
}
|
|
121
|
+
const choice = chunk.choices?.[0];
|
|
119
122
|
if (!choice)
|
|
120
123
|
continue;
|
|
121
124
|
if (choice.delta?.content) {
|
|
@@ -123,25 +126,17 @@ class OpenAiModelProvider extends BaseModelProvider {
|
|
|
123
126
|
yield { kind: 'partial', partial: safePartialJson(partial) };
|
|
124
127
|
}
|
|
125
128
|
if (choice.delta?.tool_calls) {
|
|
126
|
-
|
|
127
|
-
if (!call.function?.name || !call.id)
|
|
128
|
-
continue;
|
|
129
|
-
yield {
|
|
130
|
-
kind: 'tool_call',
|
|
131
|
-
call: {
|
|
132
|
-
id: call.id,
|
|
133
|
-
name: call.function.name,
|
|
134
|
-
arguments: parseToolArgs(call.function.arguments, req, 'objectStream')
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
}
|
|
129
|
+
accumulateToolCallDeltas(toolState, choice.delta.tool_calls);
|
|
138
130
|
}
|
|
139
|
-
if (
|
|
140
|
-
|
|
131
|
+
if (choice.finish_reason) {
|
|
132
|
+
finishReason = toFinishReason(choice.finish_reason);
|
|
141
133
|
}
|
|
142
134
|
}
|
|
135
|
+
for (const call of finalizeStreamToolCalls(toolState, req, 'objectStream')) {
|
|
136
|
+
yield { kind: 'tool_call', call };
|
|
137
|
+
}
|
|
143
138
|
const object = parseJson(partial || '{}', req, 'objectStream');
|
|
144
|
-
yield { kind: 'finish', object, usage, finishReason
|
|
139
|
+
yield { kind: 'finish', object, usage, finishReason };
|
|
145
140
|
}
|
|
146
141
|
async doEmbed(req) {
|
|
147
142
|
req.signal.throwIfAborted();
|
|
@@ -200,11 +195,14 @@ async function createChatCompletion(client, req, stream) {
|
|
|
200
195
|
model: req.model,
|
|
201
196
|
messages,
|
|
202
197
|
stream,
|
|
198
|
+
// OpenAI only emits a usage chunk during streaming when this is set.
|
|
199
|
+
...(stream ? { stream_options: { include_usage: true } } : {}),
|
|
203
200
|
tools: toTools(req.tools),
|
|
204
201
|
temperature: req.call?.temperature ?? req.defaults?.temperature,
|
|
205
202
|
max_tokens: req.call?.maxTokens ?? req.defaults?.maxTokens,
|
|
206
203
|
top_p: req.call?.topP ?? req.defaults?.topP,
|
|
207
204
|
stop: req.call?.stopSequences ?? req.defaults?.stopSequences,
|
|
205
|
+
...(req.tools && (req.call?.parallelToolCalls ?? req.defaults?.parallelToolCalls) !== undefined ? { parallel_tool_calls: req.call?.parallelToolCalls ?? req.defaults?.parallelToolCalls } : {}),
|
|
208
206
|
response_format: toResponseFormat(req),
|
|
209
207
|
...bodyOptions
|
|
210
208
|
}, { ...requestOptions, signal: req.signal });
|
|
@@ -287,6 +285,29 @@ function toTools(tools) {
|
|
|
287
285
|
}
|
|
288
286
|
}));
|
|
289
287
|
}
|
|
288
|
+
function accumulateToolCallDeltas(state, deltas) {
|
|
289
|
+
for (const delta of deltas) {
|
|
290
|
+
const index = typeof delta?.index === 'number' ? delta.index : 0;
|
|
291
|
+
const existing = state.get(index) ?? { args: '' };
|
|
292
|
+
if (delta?.id)
|
|
293
|
+
existing.id = String(delta.id);
|
|
294
|
+
if (delta?.function?.name)
|
|
295
|
+
existing.name = String(delta.function.name);
|
|
296
|
+
if (typeof delta?.function?.arguments === 'string')
|
|
297
|
+
existing.args += delta.function.arguments;
|
|
298
|
+
state.set(index, existing);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function finalizeStreamToolCalls(state, req, method) {
|
|
302
|
+
return [...state.entries()]
|
|
303
|
+
.sort((a, b) => a[0] - b[0])
|
|
304
|
+
.filter(([, call]) => call.id && call.name)
|
|
305
|
+
.map(([, call]) => ({
|
|
306
|
+
id: call.id,
|
|
307
|
+
name: call.name,
|
|
308
|
+
arguments: parseToolArgs(call.args || undefined, req, method)
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
290
311
|
function parseToolArgs(argumentsText, req, method) {
|
|
291
312
|
if (!argumentsText)
|
|
292
313
|
return {};
|