@@ -2,15 +2,28 @@ import 'dart:convert';
2
2
import 'package:http/http.dart' as http;
3
3
import 'package:chatgpt_client/src/message.dart' ;
4
4
5
+ /// A class to interact with OpenAI ChatGPT Completions API
6
+ /// Support various models such as gpt-3.5-turbo, gpt-4, etc
5
7
class ChatGPTClient {
8
+
9
+ /// OpenAI ChatGPT Completions API Endpoint URL
6
10
final url = Uri .https ("api.openai.com" , "/v1/chat/completions" );
11
+
12
+ /// OpenAI API Key which you can get from https://openai.com/api
7
13
final String apiKey;
14
+
15
+ /// GPT Model (gpt-3.5-turbo, gpt-4, etc) default to gpt-3.5-turbo
8
16
final String model;
17
+
18
+ /// System prompt, default to "You're a helpful assistant"
9
19
final String systemPrompt;
20
+
21
+ /// Temperature, default to 0.5
10
22
final double temperature;
11
23
12
24
List <Message > _historyList = List .empty (growable: true );
13
25
26
+ /// Initializer, API key is required
14
27
ChatGPTClient (
15
28
{required this .apiKey,
16
29
this .model = "gpt-3.5-turbo" ,
@@ -24,7 +37,7 @@ class ChatGPTClient {
24
37
};
25
38
}
26
39
27
- String _getBody (String text, bool stream) {
40
+ String _getBody (String text, bool stream) {
28
41
final body = {
29
42
"model" : model,
30
43
"temperature" : temperature,
@@ -35,14 +48,17 @@ class ChatGPTClient {
35
48
}
36
49
37
50
List <Message > _generateMessages (String prompt) {
38
- var messages = [_getSystemMessage ()] + _historyList + [Message (content: prompt, role: "user" )];
39
- final messagesContentCount = messages.map ((e) => e.content.length)
40
- .reduce ((value, element) => value + element);
51
+ var messages = [_getSystemMessage ()] +
52
+ _historyList +
53
+ [Message (content: prompt, role: "user" )];
54
+ final messagesContentCount = messages
55
+ .map ((e) => e.content.length)
56
+ .reduce ((value, element) => value + element);
41
57
if (messagesContentCount > (4000 * 4 )) {
42
- _historyList.removeAt (0 );
43
- messages = _generateMessages (prompt);
44
- }
45
- return messages;
58
+ _historyList.removeAt (0 );
59
+ messages = _generateMessages (prompt);
60
+ }
61
+ return messages;
46
62
}
47
63
48
64
void _appendToHistoryList (String userText, String responseText) {
@@ -56,77 +72,80 @@ class ChatGPTClient {
56
72
return Message (content: systemPrompt, role: "system" );
57
73
}
58
74
75
+ /// Send message to ChatGPT to a prompt asynchronously
59
76
Future <String > sendMessage (String text) async {
60
- final response = await http.Client ().post (url,
61
- headers: _getHeaders (), body: _getBody (text, false ));
62
-
63
- dynamic decodedResponse;
64
- if (response.contentLength != null ) {
65
- decodedResponse = jsonDecode (utf8.decode (response.bodyBytes)) as Map ;
66
- }
77
+ final response = await http.Client ()
78
+ .post (url, headers: _getHeaders (), body: _getBody (text, false ));
67
79
68
- final statusCode = response.statusCode;
69
- if (! (statusCode >= 200 && statusCode < 300 )) {
70
- if (decodedResponse != null ) {
71
- final errorMessage = decodedResponse["error" ]["message" ] as String ;
72
- throw Exception ("($statusCode ) $errorMessage " );
73
- }
74
- throw Exception ("($statusCode ) Bad response ${response .reasonPhrase ?? "" }" );
80
+ dynamic decodedResponse;
81
+ if (response.contentLength != null ) {
82
+ decodedResponse = jsonDecode (utf8.decode (response.bodyBytes)) as Map ;
83
+ }
84
+
85
+ final statusCode = response.statusCode;
86
+ if (! (statusCode >= 200 && statusCode < 300 )) {
87
+ if (decodedResponse != null ) {
88
+ final errorMessage = decodedResponse["error" ]["message" ] as String ;
89
+ throw Exception ("($statusCode ) $errorMessage " );
75
90
}
76
-
77
- final choices = decodedResponse["choices" ] as List ;
78
- final choice = choices[0 ] as Map ;
79
- final content = choice["message" ]["content" ] as String ;
80
- _appendToHistoryList (text, content);
81
- return content;
91
+ throw Exception (
92
+ "($statusCode ) Bad response ${response .reasonPhrase ?? "" }" );
93
+ }
94
+
95
+ final choices = decodedResponse["choices" ] as List ;
96
+ final choice = choices[0 ] as Map ;
97
+ final content = choice["message" ]["content" ] as String ;
98
+ _appendToHistoryList (text, content);
99
+ return content;
82
100
}
83
101
102
+ // Send Message to ChatGPT and receives the streamed response in chunk
84
103
Stream <String > sendMessageStream (String text) async * {
85
- final request = http.Request ("POST" , url)
86
- ..headers.addAll (_getHeaders ());
87
- request.body = _getBody (text, true );
88
-
89
- final responseStream = await request.send ();
90
- final statusCode = responseStream.statusCode;
91
- final byteStream = responseStream.stream;
92
-
93
- if (! (statusCode >= 200 && statusCode < 300 )) {
94
- var error = "" ;
95
- await for (final byte in byteStream) {
96
- final decoded = utf8.decode (byte).trim ();
97
- final map = jsonDecode (decoded) as Map ;
98
- final errorMessage = map["error" ]["message" ] as String ;
99
- error += errorMessage;
100
- }
101
- throw Exception ("($statusCode ) ${error .isEmpty ? "Bad Response" : error }" );
102
- }
104
+ final request = http.Request ("POST" , url)..headers.addAll (_getHeaders ());
105
+ request.body = _getBody (text, true );
106
+
107
+ final responseStream = await request.send ();
108
+ final statusCode = responseStream.statusCode;
109
+ final byteStream = responseStream.stream;
103
110
104
- var responseText = "" ;
111
+ if (! (statusCode >= 200 && statusCode < 300 )) {
112
+ var error = "" ;
105
113
await for (final byte in byteStream) {
106
- var decoded = utf8.decode (byte);
107
- final strings = decoded.split ("data: " );
108
- for (final string in strings) {
109
- final trimmedString = string.trim ();
110
- if (trimmedString.isNotEmpty && ! trimmedString.endsWith ("[DONE]" )) {
111
- final map = jsonDecode (trimmedString) as Map ;
112
- final choices = map["choices" ] as List ;
113
- final delta = choices[0 ]["delta" ] as Map ;
114
- if (delta["content" ] != null ) {
115
- final content = delta["content" ] as String ;
116
- responseText += content;
117
- yield content;
118
- }
114
+ final decoded = utf8.decode (byte).trim ();
115
+ final map = jsonDecode (decoded) as Map ;
116
+ final errorMessage = map["error" ]["message" ] as String ;
117
+ error += errorMessage;
118
+ }
119
+ throw Exception (
120
+ "($statusCode ) ${error .isEmpty ? "Bad Response" : error }" );
121
+ }
122
+
123
+ var responseText = "" ;
124
+ await for (final byte in byteStream) {
125
+ var decoded = utf8.decode (byte);
126
+ final strings = decoded.split ("data: " );
127
+ for (final string in strings) {
128
+ final trimmedString = string.trim ();
129
+ if (trimmedString.isNotEmpty && ! trimmedString.endsWith ("[DONE]" )) {
130
+ final map = jsonDecode (trimmedString) as Map ;
131
+ final choices = map["choices" ] as List ;
132
+ final delta = choices[0 ]["delta" ] as Map ;
133
+ if (delta["content" ] != null ) {
134
+ final content = delta["content" ] as String ;
135
+ responseText += content;
136
+ yield content;
119
137
}
120
138
}
139
+ }
121
140
}
122
141
123
142
if (responseText.isNotEmpty) {
124
143
_appendToHistoryList (text, responseText);
125
144
}
126
145
}
127
146
147
+ /// Clear history list array
128
148
void clearHistoryList () {
129
149
_historyList = List .empty (growable: true );
130
150
}
131
151
}
132
-
0 commit comments