@proteinjs/conversation 1.7.4 → 2.0.0
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/CHANGELOG.md +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/src/CodegenConversation.d.ts.map +1 -1
- package/dist/src/CodegenConversation.js +9 -6
- package/dist/src/CodegenConversation.js.map +1 -1
- package/dist/src/Conversation.d.ts +30 -6
- package/dist/src/Conversation.d.ts.map +1 -1
- package/dist/src/Conversation.js +119 -47
- package/dist/src/Conversation.js.map +1 -1
- package/dist/src/OpenAi.d.ts +57 -15
- package/dist/src/OpenAi.d.ts.map +1 -1
- package/dist/src/OpenAi.js +148 -124
- package/dist/src/OpenAi.js.map +1 -1
- package/dist/src/OpenAiStreamProcessor.d.ts +5 -1
- package/dist/src/OpenAiStreamProcessor.d.ts.map +1 -1
- package/dist/src/OpenAiStreamProcessor.js +25 -5
- package/dist/src/OpenAiStreamProcessor.js.map +1 -1
- package/dist/src/UsageData.d.ts +38 -0
- package/dist/src/UsageData.d.ts.map +1 -0
- package/dist/src/UsageData.js +47 -0
- package/dist/src/UsageData.js.map +1 -0
- package/dist/src/code_template/Code.js +1 -1
- package/dist/src/code_template/Code.js.map +1 -1
- package/dist/test/openai/openai.generateList.test.js +1 -1
- package/dist/test/openai/openai.generateList.test.js.map +1 -1
- package/index.ts +1 -0
- package/package.json +5 -5
- package/src/CodegenConversation.ts +6 -3
- package/src/Conversation.ts +87 -80
- package/src/OpenAi.ts +197 -210
- package/src/OpenAiStreamProcessor.ts +25 -6
- package/src/UsageData.ts +76 -0
- package/src/code_template/Code.ts +1 -1
- package/test/openai/openai.generateList.test.ts +3 -3
|
@@ -2,6 +2,7 @@ import { ChatCompletionMessageToolCall, ChatCompletionChunk } from 'openai/resou
|
|
|
2
2
|
import { LogLevel, Logger } from '@proteinjs/logger';
|
|
3
3
|
import { Stream } from 'openai/streaming';
|
|
4
4
|
import { Readable, Transform, TransformCallback, PassThrough } from 'stream';
|
|
5
|
+
import { UsageData, UsageDataAccumulator } from './UsageData';
|
|
5
6
|
|
|
6
7
|
export interface AssistantResponseStreamChunk {
|
|
7
8
|
content?: string;
|
|
@@ -21,6 +22,7 @@ export class OpenAiStreamProcessor {
|
|
|
21
22
|
private inputStream: Readable;
|
|
22
23
|
private controlStream: Transform;
|
|
23
24
|
private outputStream: Readable;
|
|
25
|
+
private outputStreamTerminated = false;
|
|
24
26
|
|
|
25
27
|
constructor(
|
|
26
28
|
inputStream: Stream<ChatCompletionChunk>,
|
|
@@ -28,8 +30,10 @@ export class OpenAiStreamProcessor {
|
|
|
28
30
|
toolCalls: ChatCompletionMessageToolCall[],
|
|
29
31
|
currentFunctionCalls: number
|
|
30
32
|
) => Promise<Readable>,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
private usageDataAccumulator: UsageDataAccumulator,
|
|
34
|
+
logLevel?: LogLevel,
|
|
35
|
+
private abortSignal?: AbortSignal,
|
|
36
|
+
private onUsageData?: (usageData: UsageData) => Promise<void>
|
|
33
37
|
) {
|
|
34
38
|
this.logger = new Logger({ name: this.constructor.name, logLevel });
|
|
35
39
|
this.inputStream = Readable.from(inputStream);
|
|
@@ -50,6 +54,7 @@ export class OpenAiStreamProcessor {
|
|
|
50
54
|
* @returns a `Transform` that parses the input stream and delegates to tool calls or writes a text response to the user
|
|
51
55
|
*/
|
|
52
56
|
private createControlStream(): Transform {
|
|
57
|
+
let finishedProcessingToolCallStream = false;
|
|
53
58
|
return new Transform({
|
|
54
59
|
objectMode: true,
|
|
55
60
|
transform: (chunk: ChatCompletionChunk, encoding: string, callback: TransformCallback) => {
|
|
@@ -68,21 +73,35 @@ export class OpenAiStreamProcessor {
|
|
|
68
73
|
} else if (chunk.choices[0]?.delta?.tool_calls) {
|
|
69
74
|
this.handleToolCallDelta(chunk.choices[0].delta.tool_calls);
|
|
70
75
|
} else if (chunk.choices[0]?.finish_reason === 'tool_calls') {
|
|
71
|
-
|
|
76
|
+
finishedProcessingToolCallStream = true;
|
|
72
77
|
} else if (chunk.choices[0]?.finish_reason === 'stop') {
|
|
73
78
|
this.outputStream.push({ finishReason: 'stop' } as AssistantResponseStreamChunk);
|
|
74
79
|
this.outputStream.push(null);
|
|
75
|
-
this.
|
|
80
|
+
this.outputStreamTerminated = true;
|
|
76
81
|
} else if (chunk.choices[0]?.finish_reason === 'length') {
|
|
77
82
|
this.logger.info({ message: `The maximum number of output tokens was reached` });
|
|
78
83
|
this.outputStream.push({ finishReason: 'length' } as AssistantResponseStreamChunk);
|
|
79
84
|
this.outputStream.push(null);
|
|
80
|
-
this.
|
|
85
|
+
this.outputStreamTerminated = true;
|
|
81
86
|
} else if (chunk.choices[0]?.finish_reason === 'content_filter') {
|
|
82
87
|
this.logger.warn({ message: `Content was omitted due to a flag from OpenAI's content filters` });
|
|
83
88
|
this.outputStream.push({ finishReason: 'content_filter' } as AssistantResponseStreamChunk);
|
|
84
89
|
this.outputStream.push(null);
|
|
85
|
-
this.
|
|
90
|
+
this.outputStreamTerminated = true;
|
|
91
|
+
} else if (chunk.usage) {
|
|
92
|
+
this.usageDataAccumulator.addTokenUsage({
|
|
93
|
+
promptTokens: chunk.usage.prompt_tokens,
|
|
94
|
+
completionTokens: chunk.usage.completion_tokens,
|
|
95
|
+
totalTokens: chunk.usage.total_tokens,
|
|
96
|
+
});
|
|
97
|
+
if (finishedProcessingToolCallStream) {
|
|
98
|
+
this.handleToolCalls();
|
|
99
|
+
} else if (this.outputStreamTerminated) {
|
|
100
|
+
if (this.onUsageData) {
|
|
101
|
+
this.onUsageData(this.usageDataAccumulator.usageData);
|
|
102
|
+
}
|
|
103
|
+
this.destroyStreams();
|
|
104
|
+
}
|
|
86
105
|
}
|
|
87
106
|
callback();
|
|
88
107
|
} catch (error: any) {
|
package/src/UsageData.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { TiktokenModel } from 'tiktoken';
|
|
2
|
+
|
|
3
|
+
export type TokenUsage = {
|
|
4
|
+
promptTokens: number;
|
|
5
|
+
completionTokens: number;
|
|
6
|
+
totalTokens: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Usage data accumulated throughout the lifecycle of a single call to
|
|
11
|
+
* `OpenAi.generateResponse` or `OpenAi.generateStreamingResponse`.
|
|
12
|
+
*/
|
|
13
|
+
export type UsageData = {
|
|
14
|
+
/** The model used by the assistant */
|
|
15
|
+
model: TiktokenModel;
|
|
16
|
+
/** The token usage of the initial request sent to the assistant */
|
|
17
|
+
initialRequestTokenUsage: TokenUsage;
|
|
18
|
+
/** The total token usage of all requests sent to the assistant (ie. initial request + all subsequent tool call requests) */
|
|
19
|
+
totalTokenUsage: TokenUsage;
|
|
20
|
+
/** The number of requests sent to the assistant */
|
|
21
|
+
totalRequestsToAssistant: number;
|
|
22
|
+
/** The number of times each tool was called by the assistant */
|
|
23
|
+
callsPerTool: { [toolName: string]: number };
|
|
24
|
+
/** The total number of tool calls made by the assistant */
|
|
25
|
+
totalToolCalls: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type UsageDataAccumulatorParams = {
|
|
29
|
+
model: TiktokenModel;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export class UsageDataAccumulator {
|
|
33
|
+
private processedInitialRequest = false;
|
|
34
|
+
public usageData: UsageData;
|
|
35
|
+
|
|
36
|
+
constructor({ model }: UsageDataAccumulatorParams) {
|
|
37
|
+
this.usageData = {
|
|
38
|
+
model,
|
|
39
|
+
initialRequestTokenUsage: {
|
|
40
|
+
promptTokens: 0,
|
|
41
|
+
completionTokens: 0,
|
|
42
|
+
totalTokens: 0,
|
|
43
|
+
},
|
|
44
|
+
totalTokenUsage: {
|
|
45
|
+
promptTokens: 0,
|
|
46
|
+
completionTokens: 0,
|
|
47
|
+
totalTokens: 0,
|
|
48
|
+
},
|
|
49
|
+
totalRequestsToAssistant: 0,
|
|
50
|
+
callsPerTool: {},
|
|
51
|
+
totalToolCalls: 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
addTokenUsage(tokenUsage: TokenUsage) {
|
|
56
|
+
this.usageData.totalRequestsToAssistant++;
|
|
57
|
+
if (!this.processedInitialRequest) {
|
|
58
|
+
this.usageData.initialRequestTokenUsage = tokenUsage;
|
|
59
|
+
this.processedInitialRequest = true;
|
|
60
|
+
}
|
|
61
|
+
this.usageData.totalTokenUsage = {
|
|
62
|
+
promptTokens: this.usageData.totalTokenUsage.promptTokens + tokenUsage.promptTokens,
|
|
63
|
+
completionTokens: this.usageData.totalTokenUsage.completionTokens + tokenUsage.completionTokens,
|
|
64
|
+
totalTokens: this.usageData.totalTokenUsage.totalTokens + tokenUsage.totalTokens,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
recordToolCall(toolName: string) {
|
|
69
|
+
if (!this.usageData.callsPerTool[toolName]) {
|
|
70
|
+
this.usageData.callsPerTool[toolName] = 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.usageData.callsPerTool[toolName]++;
|
|
74
|
+
this.usageData.totalToolCalls++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -31,7 +31,7 @@ export class Code {
|
|
|
31
31
|
this.addImports(this.args.imports, this.args.conversation);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
return await this.args.conversation.generateCode(this.args.description, 'gpt-4');
|
|
34
|
+
return await this.args.conversation.generateCode({ description: this.args.description, model: 'gpt-4' });
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
private addImports(imports: Import[], conversation: Conversation) {
|
|
@@ -2,7 +2,7 @@ import { OpenAi } from '../../src/OpenAi';
|
|
|
2
2
|
|
|
3
3
|
test('generateList should return an array of numbers, counting to 10', async () => {
|
|
4
4
|
const numbers = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
|
|
5
|
-
expect(
|
|
6
|
-
numbers.join(' ')
|
|
7
|
-
);
|
|
5
|
+
expect(
|
|
6
|
+
(await new OpenAi().generateList({ messages: [`Create a list of numbers spelled out, from 1 to 10`] })).join(' ')
|
|
7
|
+
).toBe(numbers.join(' '));
|
|
8
8
|
});
|