Fix open-ai early termination & memory improvements
Publish Library / Build NPM Project (push) Successful in 48s
Publish Library / Tag Version (push) Successful in 7s

This commit is contained in:
2026-09-19 19:27:00 -04:00
parent 1e8c7c6662
commit 263a65c192
2 changed files with 340 additions and 239 deletions
+43 -10
View File
@@ -36,8 +36,10 @@ export class OpenAi extends LLMProvider {
private toWire(history: LLMMessage[], system?: string): any[] {
const wire: any[] = [];
if(system) wire.push({role: 'system', content: system});
for(let i = 0; i < history.length; i++) {
const h = history[i];
if(h.role !== 'tool') {
wire.push({role: h.role, content: this.toWireContent(h.content)});
continue;
@@ -45,24 +47,34 @@ export class OpenAi extends LLMProvider {
const calls: any[] = [];
const results: any[] = [];
while(i < history.length && history[i].role === 'tool') {
const tool = <any>history[i];
const tool: any = history[i];
calls.push({
id: tool.id,
type: 'function',
function: {
name: tool.name,
arguments: JSON.stringify(tool.args)
arguments: JSON.stringify(tool.args || {})
}
});
results.push({
role: 'tool',
tool_call_id: tool.id,
content: tool.error || tool.content || ''
});
i++;
}
wire.push({role: 'assistant', content: null, tool_calls: calls});
wire.push({
role: 'assistant',
content: null,
tool_calls: calls
});
wire.push(...results);
i--;
}
@@ -76,13 +88,12 @@ export class OpenAi extends LLMProvider {
if(!options.history) options.history = [];
const history = options.history;
if(message) history.push({role: 'user', content: message, timestamp: Date.now()});
const tools = options.tools || this.ai.options.llm?.tools || [];
const requestParams: any = {
model: options.model || this.model,
stream: !!options.stream,
max_completion_tokens: options.maxTokens || this.ai.options.llm?.maxTokens || undefined,
temperature: options.temperature || this.ai.options.llm?.temperature || undefined,
max_completion_tokens: options.maxTokens ?? this.ai.options.llm?.maxTokens,
temperature: options.temperature ?? this.ai.options.llm?.temperature,
tools: tools.map(t => ({
type: 'function',
function: {
@@ -112,7 +123,10 @@ export class OpenAi extends LLMProvider {
try {
let terminal = false;
let iteration = 0;
do {
iteration++;
requestParams.messages = this.toWire(history.filter(h => h.role !== 'system'), options.system);
const callStart = Date.now();
@@ -126,36 +140,46 @@ export class OpenAi extends LLMProvider {
let usage: any;
let finishReason: string | undefined;
let msg: any = {content: '', tool_calls: []};
let streamedChars = 0;
if(options.stream) {
let streamCompleted = false;
try {
for await (const chunk of resp) {
if(controller.signal.aborted) break;
if(chunk.usage) usage = chunk.usage;
const choice = chunk.choices?.[0];
if(choice?.finish_reason) finishReason = choice.finish_reason;
if(choice?.delta?.content) {
msg.content += choice.delta.content;
streamedChars += choice.delta.content.length;
options.stream({text: choice.delta.content});
}
if(choice?.delta?.tool_calls) {
for(const deltaTC of choice.delta.tool_calls) {
const index = deltaTC.index ?? msg.tool_calls.length;
let existing = msg.tool_calls.find((tc: any) => tc.index === index);
if(!existing) {
existing = {index, id: '', function: {name: '', arguments: ''}};
msg.tool_calls.push(existing);
}
if(deltaTC.id) existing.id = deltaTC.id;
if(deltaTC.function?.name) existing.function.name = deltaTC.function.name;
if(deltaTC.function?.arguments) existing.function.arguments += deltaTC.function.arguments;
}
}
}
streamCompleted = true;
} catch(err) {
if(!controller.signal.aborted) throw err;
}
if(streamCompleted && !finishReason) finishReason = msg.tool_calls.length ? 'tool_calls' : 'stop';
} else {
usage = resp.usage;
@@ -164,9 +188,7 @@ export class OpenAi extends LLMProvider {
}
const duration = Date.now() - callStart;
const tps = usage?.completion_tokens && duration > 0
? usage.completion_tokens / (duration / 1000)
: 0;
const tps = usage?.completion_tokens && duration > 0 ? usage.completion_tokens / (duration / 1000) : 0;
if(finishReason === 'length' && !controller.signal.aborted) {
if(msg.content?.trim()) history.push({role: 'assistant', content: msg.content.trim(), timestamp: Date.now(), duration, tps});
@@ -178,10 +200,20 @@ export class OpenAi extends LLMProvider {
}
const toolCalls = msg.tool_calls || [];
if(toolCalls.length && !controller.signal.aborted) {
if(msg.content?.trim()) history.push({role: 'assistant', content: msg.content.trim(), timestamp: Date.now(), duration, tps});
const entries = toolCalls.map((tc: any) => {
const entry: any = {role: 'tool', id: tc.id, name: tc.function.name, args: JSONAttemptParse(tc.function.arguments, {}), content: undefined, timestamp: Date.now()};
const entry: any = {
role: 'tool',
id: tc.id,
name: tc.function.name,
args: JSONAttemptParse(tc.function.arguments, {}),
content: undefined,
timestamp: Date.now()
};
history.push(entry);
return {tc, entry};
});
@@ -195,6 +227,7 @@ export class OpenAi extends LLMProvider {
if(chunk.done) return;
options.stream!(chunk);
});
const result = await tool.fn(entry.args, toolStream, this.ai, tc.id);
entry.content = typeof result === 'object' ? JSONSanitize(result) : result;
} catch(err: any) {