chatrepl 0.1.0a0__tar.gz

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.

Potentially problematic release.


This version of chatrepl might be problematic. Click here for more details.

@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jifeng Wu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: chatrepl
3
+ Version: 0.1.0a0
4
+ Summary: A Python 2.7+ REPL for interacting with LLMs with an OpenAI Chat Completions-compatible API.
5
+ Author-email: Jifeng Wu <jifengwu2k@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jifengwu2k/chatrepl
8
+ Project-URL: Bug Tracker, https://github.com/jifengwu2k/chatrepl/issues
9
+ Classifier: Programming Language :: Python :: 2
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=2
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ A Python 2.7+ REPL for interacting with LLMs with an OpenAI Chat Completions-compatible API.
18
+
19
+ ## Features
20
+
21
+ - **Zero Dependencies:** Works with stock Python 2.7+ and 3.x
22
+ - **Interactive Chat:** Natural REPL interface with conversation tracking
23
+ - **Streaming Responses:** See responses as they're generated
24
+ - **Multiline Input:** Support for complex prompts with `:multiline`
25
+ - **File Integration:** Load prompts from text files with `:send <textfile>`
26
+ - **Conversation Persistence:** Save/load complete conversations in JSON format
27
+ - **Enhanced Input:** Optional readline support for history and line editing
28
+ - **Dual Modes:** Both interactive REPL and pipe-friendly CLI
29
+
30
+ ## Interactive Mode
31
+
32
+ ```bash
33
+ $ python -m terminal_chat \
34
+ --api-key "your-api-key" \
35
+ --base-url "https://api.openai.com/v1" \
36
+ --model "gpt-4o"
37
+ ```
38
+
39
+ ### Basic Conversation
40
+
41
+ ```text
42
+ User [1]: Explain recursion to a 5-year-old
43
+
44
+ Assistant [1]: Imagine you're holding a doll that has...
45
+ ```
46
+
47
+ ### Using Files
48
+ ```text
49
+ User [2]: :send code.py
50
+ Assistant [2]: I notice this Python code could be improved...
51
+
52
+ User [3]: :save review_chat.json
53
+ ```
54
+
55
+ ### Multiline Input
56
+
57
+ ```text
58
+ User [4]: :multiline
59
+ Enter EOF on a blank line to finish input:
60
+ > Compare these programming languages:
61
+ > 1. Python
62
+ > 2. Rust
63
+ > 3. Go
64
+ > [Ctrl-D]
65
+
66
+ Assistant [4]: Here's a comparison:
67
+ 1. Python - High-level, interpreted...
68
+ 2. Rust - Systems programming...
69
+ 3. Go - Compiled, concurrent...
70
+ ```
71
+
72
+ ## Non-interactive Mode (Piped Input)
73
+
74
+ ```bash
75
+ $ uname -a | python -m terminal_chat --api-key <your_api_key> --base-url <your_base_url> --model <model_name>
76
+ The output you've provided appears to be system information from ... [output streamed to STDOUT]
77
+ ```
78
+
79
+ ## Print Saved Conversations
80
+
81
+ ```bash
82
+ $ python -m terminal_chat --print conversation.json
83
+ User [1]: ...
84
+
85
+ Assistant [1]: ...
86
+ ```
87
+
88
+ ## Interactive Commands
89
+
90
+ - `:multiline` - Enter multiline input mode (end with blank line + Ctrl-D)
91
+ - `:send TEXTFILE` - Send contents of TEXTFILE
92
+ - `:load JSONFILE` - Load conversation from JSONFILE
93
+ - `:save JSONFILE` - Save conversation to JSONFILE
94
+ - `:help` - Show help
95
+ - `:quit` or `Ctrl-D` - Exit the program
96
+
97
+ ## Best Practices
98
+
99
+ 1. For long sessions, periodically save with `:save`
100
+ 2. Use `:multiline` for structured prompts (lists, code, etc.)
101
+ 3. JSON files can be edited manually for prompt engineering
102
+
103
+ ## Contributing
104
+
105
+ Contributions are welcome! Please submit pull requests or open issues on the GitHub repository.
106
+
107
+ ## License
108
+
109
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,93 @@
1
+ A Python 2.7+ REPL for interacting with LLMs with an OpenAI Chat Completions-compatible API.
2
+
3
+ ## Features
4
+
5
+ - **Zero Dependencies:** Works with stock Python 2.7+ and 3.x
6
+ - **Interactive Chat:** Natural REPL interface with conversation tracking
7
+ - **Streaming Responses:** See responses as they're generated
8
+ - **Multiline Input:** Support for complex prompts with `:multiline`
9
+ - **File Integration:** Load prompts from text files with `:send <textfile>`
10
+ - **Conversation Persistence:** Save/load complete conversations in JSON format
11
+ - **Enhanced Input:** Optional readline support for history and line editing
12
+ - **Dual Modes:** Both interactive REPL and pipe-friendly CLI
13
+
14
+ ## Interactive Mode
15
+
16
+ ```bash
17
+ $ python -m terminal_chat \
18
+ --api-key "your-api-key" \
19
+ --base-url "https://api.openai.com/v1" \
20
+ --model "gpt-4o"
21
+ ```
22
+
23
+ ### Basic Conversation
24
+
25
+ ```text
26
+ User [1]: Explain recursion to a 5-year-old
27
+
28
+ Assistant [1]: Imagine you're holding a doll that has...
29
+ ```
30
+
31
+ ### Using Files
32
+ ```text
33
+ User [2]: :send code.py
34
+ Assistant [2]: I notice this Python code could be improved...
35
+
36
+ User [3]: :save review_chat.json
37
+ ```
38
+
39
+ ### Multiline Input
40
+
41
+ ```text
42
+ User [4]: :multiline
43
+ Enter EOF on a blank line to finish input:
44
+ > Compare these programming languages:
45
+ > 1. Python
46
+ > 2. Rust
47
+ > 3. Go
48
+ > [Ctrl-D]
49
+
50
+ Assistant [4]: Here's a comparison:
51
+ 1. Python - High-level, interpreted...
52
+ 2. Rust - Systems programming...
53
+ 3. Go - Compiled, concurrent...
54
+ ```
55
+
56
+ ## Non-interactive Mode (Piped Input)
57
+
58
+ ```bash
59
+ $ uname -a | python -m terminal_chat --api-key <your_api_key> --base-url <your_base_url> --model <model_name>
60
+ The output you've provided appears to be system information from ... [output streamed to STDOUT]
61
+ ```
62
+
63
+ ## Print Saved Conversations
64
+
65
+ ```bash
66
+ $ python -m terminal_chat --print conversation.json
67
+ User [1]: ...
68
+
69
+ Assistant [1]: ...
70
+ ```
71
+
72
+ ## Interactive Commands
73
+
74
+ - `:multiline` - Enter multiline input mode (end with blank line + Ctrl-D)
75
+ - `:send TEXTFILE` - Send contents of TEXTFILE
76
+ - `:load JSONFILE` - Load conversation from JSONFILE
77
+ - `:save JSONFILE` - Save conversation to JSONFILE
78
+ - `:help` - Show help
79
+ - `:quit` or `Ctrl-D` - Exit the program
80
+
81
+ ## Best Practices
82
+
83
+ 1. For long sessions, periodically save with `:save`
84
+ 2. Use `:multiline` for structured prompts (lists, code, etc.)
85
+ 3. JSON files can be edited manually for prompt engineering
86
+
87
+ ## Contributing
88
+
89
+ Contributions are welcome! Please submit pull requests or open issues on the GitHub repository.
90
+
91
+ ## License
92
+
93
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: chatrepl
3
+ Version: 0.1.0a0
4
+ Summary: A Python 2.7+ REPL for interacting with LLMs with an OpenAI Chat Completions-compatible API.
5
+ Author-email: Jifeng Wu <jifengwu2k@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jifengwu2k/chatrepl
8
+ Project-URL: Bug Tracker, https://github.com/jifengwu2k/chatrepl/issues
9
+ Classifier: Programming Language :: Python :: 2
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=2
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ A Python 2.7+ REPL for interacting with LLMs with an OpenAI Chat Completions-compatible API.
18
+
19
+ ## Features
20
+
21
+ - **Zero Dependencies:** Works with stock Python 2.7+ and 3.x
22
+ - **Interactive Chat:** Natural REPL interface with conversation tracking
23
+ - **Streaming Responses:** See responses as they're generated
24
+ - **Multiline Input:** Support for complex prompts with `:multiline`
25
+ - **File Integration:** Load prompts from text files with `:send <textfile>`
26
+ - **Conversation Persistence:** Save/load complete conversations in JSON format
27
+ - **Enhanced Input:** Optional readline support for history and line editing
28
+ - **Dual Modes:** Both interactive REPL and pipe-friendly CLI
29
+
30
+ ## Interactive Mode
31
+
32
+ ```bash
33
+ $ python -m terminal_chat \
34
+ --api-key "your-api-key" \
35
+ --base-url "https://api.openai.com/v1" \
36
+ --model "gpt-4o"
37
+ ```
38
+
39
+ ### Basic Conversation
40
+
41
+ ```text
42
+ User [1]: Explain recursion to a 5-year-old
43
+
44
+ Assistant [1]: Imagine you're holding a doll that has...
45
+ ```
46
+
47
+ ### Using Files
48
+ ```text
49
+ User [2]: :send code.py
50
+ Assistant [2]: I notice this Python code could be improved...
51
+
52
+ User [3]: :save review_chat.json
53
+ ```
54
+
55
+ ### Multiline Input
56
+
57
+ ```text
58
+ User [4]: :multiline
59
+ Enter EOF on a blank line to finish input:
60
+ > Compare these programming languages:
61
+ > 1. Python
62
+ > 2. Rust
63
+ > 3. Go
64
+ > [Ctrl-D]
65
+
66
+ Assistant [4]: Here's a comparison:
67
+ 1. Python - High-level, interpreted...
68
+ 2. Rust - Systems programming...
69
+ 3. Go - Compiled, concurrent...
70
+ ```
71
+
72
+ ## Non-interactive Mode (Piped Input)
73
+
74
+ ```bash
75
+ $ uname -a | python -m terminal_chat --api-key <your_api_key> --base-url <your_base_url> --model <model_name>
76
+ The output you've provided appears to be system information from ... [output streamed to STDOUT]
77
+ ```
78
+
79
+ ## Print Saved Conversations
80
+
81
+ ```bash
82
+ $ python -m terminal_chat --print conversation.json
83
+ User [1]: ...
84
+
85
+ Assistant [1]: ...
86
+ ```
87
+
88
+ ## Interactive Commands
89
+
90
+ - `:multiline` - Enter multiline input mode (end with blank line + Ctrl-D)
91
+ - `:send TEXTFILE` - Send contents of TEXTFILE
92
+ - `:load JSONFILE` - Load conversation from JSONFILE
93
+ - `:save JSONFILE` - Save conversation to JSONFILE
94
+ - `:help` - Show help
95
+ - `:quit` or `Ctrl-D` - Exit the program
96
+
97
+ ## Best Practices
98
+
99
+ 1. For long sessions, periodically save with `:save`
100
+ 2. Use `:multiline` for structured prompts (lists, code, etc.)
101
+ 3. JSON files can be edited manually for prompt engineering
102
+
103
+ ## Contributing
104
+
105
+ Contributions are welcome! Please submit pull requests or open issues on the GitHub repository.
106
+
107
+ ## License
108
+
109
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ chatrepl.py
4
+ pyproject.toml
5
+ setup.cfg
6
+ chatrepl.egg-info/PKG-INFO
7
+ chatrepl.egg-info/SOURCES.txt
8
+ chatrepl.egg-info/dependency_links.txt
9
+ chatrepl.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ chatrepl
@@ -0,0 +1,423 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ from __future__ import print_function
4
+ import argparse
5
+ import codecs
6
+ import getopt
7
+ import io
8
+ import json
9
+ import os
10
+ import os.path
11
+ import sys
12
+
13
+ # Python version compatibility setup
14
+ if sys.version_info < (3,):
15
+ from urllib2 import Request, urlopen, URLError
16
+ text_type = unicode
17
+ binary_type = str
18
+
19
+ def unicode_input(prompt):
20
+ return raw_input(prompt).decode(sys.stdin.encoding)
21
+
22
+ open_with_encoding = codecs.open
23
+
24
+ def post_request_instance(url, data, headers):
25
+ return Request(url, data=data, headers=headers)
26
+ else:
27
+ from urllib.request import Request, urlopen
28
+ from urllib.error import URLError
29
+ text_type = str
30
+ binary_type = bytes
31
+ unicode_input = input
32
+ open_with_encoding = open
33
+
34
+ def post_request_instance(url, data, headers):
35
+ return Request(url, data=data, headers=headers, method='POST')
36
+
37
+
38
+ # Are we under interactive mode?
39
+ IS_INTERACTIVE = sys.stdin.isatty()
40
+
41
+
42
+ def sanitize(text):
43
+ return text.encode(sys.stdout.encoding, errors='ignore').decode(sys.stdout.encoding)
44
+
45
+
46
+ def fputs(text, stream):
47
+ """Write text to a stream."""
48
+ try:
49
+ stream.write(text)
50
+ stream.flush()
51
+ except UnicodeEncodeError:
52
+ fputs(sanitize(text), stream)
53
+
54
+
55
+ def perror(e):
56
+ fputs(u'%s: %s\n' % (type(e).__name__, e), sys.stderr)
57
+
58
+
59
+ # Try to import readline under interactive mode
60
+ if IS_INTERACTIVE:
61
+ try:
62
+ import readline
63
+ except ImportError as e:
64
+ readline = None
65
+ perror(e)
66
+ fputs(u"\nFailed to import readline. This will affect the command-line interface functionality:\n", sys.stderr)
67
+ fputs(u" - Line editing features (arrow keys, cursor movement) will be disabled\n", sys.stderr)
68
+ fputs(u" - Command history (up/down keys) will not be available\n", sys.stderr)
69
+ fputs(u"\nWhile the program will still run, the text input will be basic and limited.\n", sys.stderr)
70
+ else:
71
+ readline = None
72
+
73
+
74
+ def save_messages_to_file(messages, filename):
75
+ """Save chat messages to a JSON file."""
76
+ with open_with_encoding(filename, 'w', encoding='utf-8') as f:
77
+ json.dump(messages, f, indent=2, ensure_ascii=False)
78
+
79
+
80
+ def load_messages_from_file(filename):
81
+ """Load chat messages from a JSON file."""
82
+ with open_with_encoding(filename, 'r', encoding='utf-8') as f:
83
+ loaded = json.load(f)
84
+ if isinstance(loaded, list) and all(
85
+ (
86
+ isinstance(message, dict)
87
+ and message.get(u'role', None) in (u'user', u'assistant')
88
+ and isinstance(message.get(u'content', None), text_type)
89
+ )
90
+ for message in loaded
91
+ ):
92
+ return loaded
93
+ else:
94
+ raise ValueError(u"Invalid JSON schema: Expected a list of dictionaries with keys 'role' ('user' or 'assistant') and 'content' (string). Got: %s" % loaded)
95
+
96
+
97
+ def print_loaded_messages(loaded_message_list):
98
+ """Print all loaded messages.
99
+ Return number of user messages."""
100
+ num_user_messages_encountered = 0
101
+
102
+ for message in loaded_message_list:
103
+ role = message[u'role']
104
+ content = message[u'content']
105
+
106
+ if role == 'user':
107
+ num_user_messages_encountered += 1
108
+
109
+ fputs(
110
+ u'\n%s [%i]: %s\n' % (
111
+ role.capitalize(),
112
+ num_user_messages_encountered,
113
+ content
114
+ ),
115
+ sys.stdout
116
+ )
117
+
118
+ return num_user_messages_encountered
119
+
120
+
121
+ def read_file_content(filename):
122
+ """Read text_type content from a file."""
123
+ with open_with_encoding(filename, 'r', encoding='utf-8') as f:
124
+ return f.read()
125
+
126
+
127
+ # Global message counter
128
+ # Like IPython's message counter, incremented for non-empty user messages
129
+ # Read in `get_single_message_content_from_model`
130
+ # Written in `get_single_message_content_from_user`, `main`
131
+ global_message_counter = 1
132
+
133
+
134
+ # Model-facing functions
135
+ def send_messages_to_model_and_stream_response(
136
+ api_key,
137
+ base_url,
138
+ model,
139
+ message_list
140
+ ):
141
+ """Send messages to the model using standard library HTTP requests."""
142
+ url = u"%s/chat/completions" % base_url
143
+ headers = {
144
+ u"Content-Type": u"application/json",
145
+ u"Authorization": u"Bearer %s" % api_key
146
+ }
147
+ data = json.dumps({
148
+ u"model": model,
149
+ u"messages": message_list,
150
+ u"stream": True
151
+ }).encode('utf-8')
152
+
153
+ req = post_request_instance(url, data=data, headers=headers)
154
+ response = urlopen(req)
155
+ for line in response:
156
+ if line.startswith(b"data: "):
157
+ chunk_data = line[6:].decode('utf-8').strip() # Remove "data: " prefix
158
+ if chunk_data == u"[DONE]":
159
+ break
160
+
161
+ chunk = json.loads(chunk_data)
162
+ content = chunk.get(u"choices", [{}])[0].get(u"delta", {}).get(u"content", "") or u""
163
+ yield content
164
+
165
+
166
+ def get_single_message_content_from_model(
167
+ api_key,
168
+ base_url,
169
+ model,
170
+ message_list
171
+ ):
172
+ global global_message_counter
173
+
174
+ fputs(
175
+ u'\nAssistant [%d]: ' % global_message_counter,
176
+ sys.stdout
177
+ )
178
+
179
+ full_response = []
180
+ for response in send_messages_to_model_and_stream_response(
181
+ api_key,
182
+ base_url,
183
+ model,
184
+ message_list
185
+ ):
186
+ fputs(response, sys.stdout)
187
+ full_response.append(response)
188
+
189
+ fputs(u'\n', sys.stdout)
190
+
191
+ return u''.join(full_response)
192
+
193
+
194
+ # User-facing functions
195
+ def display_help():
196
+ fputs(u'\nEnter a message to send to the model or use one of the following commands:\n', sys.stdout)
197
+ fputs(u':multiline Enter multiline input\n', sys.stdout)
198
+ fputs(u':send TEXTFILE Send the contents of TEXTFILE\n', sys.stdout)
199
+ fputs(u':load JSONFILE Load a conversation from JSONFILE\n', sys.stdout)
200
+ fputs(u':save JSONFILE Save the conversation to JSONFILE\n', sys.stdout)
201
+ fputs(u':help Display help\n', sys.stdout)
202
+ fputs(u':quit (or Ctrl-D) Exit the program\n', sys.stdout)
203
+
204
+
205
+ def get_single_line_input(prompt=u'> '):
206
+ """Get a single-line input from the user."""
207
+ # Do NOT use unicode_input().
208
+ # When the user enters something and presses down BACKSPACE, the prompt is removed as well.
209
+ return unicode_input(prompt)
210
+
211
+
212
+ def get_multi_line_input():
213
+ """Get multi-line input from the user until EOF."""
214
+ fputs(u'Enter EOF on a blank line to finish input:\n', sys.stdout)
215
+ lines = []
216
+ try:
217
+ while True:
218
+ line = get_single_line_input(u'> ')
219
+ lines.append(line)
220
+ except EOFError as e:
221
+ pass
222
+
223
+ return u'\n'.join(lines)
224
+
225
+
226
+ def get_single_message_content_from_user(mutable_message_list):
227
+ global global_message_counter
228
+
229
+ while True:
230
+ user_input = get_single_line_input(
231
+ u'\nUser [%d]: ' % global_message_counter
232
+ ).strip()
233
+
234
+ if user_input.startswith(u':'):
235
+ tokens = user_input.split()
236
+ cmd = tokens[0] if tokens else u""
237
+ args = tokens[1:]
238
+
239
+ # :multiline
240
+ if cmd == u":multiline" and not args:
241
+ content = get_multi_line_input()
242
+ # :send <textfile>
243
+ elif cmd == u":send" and len(args) == 1:
244
+ try:
245
+ content = read_file_content(args[0])
246
+ except Exception as e:
247
+ perror(e)
248
+
249
+ global_message_counter += 1
250
+ continue
251
+ # :load <jsonfile>
252
+ elif cmd == u":load" and len(args) == 1:
253
+ try:
254
+ loaded_messages = load_messages_from_file(args[0])
255
+ fputs(u"Loaded conversation from %s:\n" % args[0], sys.stdout)
256
+
257
+ last_message_counter = print_loaded_messages(loaded_messages)
258
+
259
+ mutable_message_list[:] = loaded_messages
260
+ global_message_counter = last_message_counter + 1
261
+ continue
262
+ except Exception as e:
263
+ perror(e)
264
+
265
+ global_message_counter += 1
266
+ continue
267
+ # :save <jsonfile>
268
+ elif cmd == u":save" and len(args) == 1:
269
+ try:
270
+ save_messages_to_file(mutable_message_list, args[0])
271
+ fputs(u"Conversation saved to %s\n" % args[0], sys.stdout)
272
+ except Exception as e:
273
+ perror(e)
274
+
275
+ global_message_counter += 1
276
+ continue
277
+ # :help
278
+ elif cmd == u":help" and not args:
279
+ display_help()
280
+
281
+ global_message_counter += 1
282
+ continue
283
+ # :quit
284
+ elif cmd == u":quit" and not args:
285
+ # Same as pressing Ctrl-D (sending EOF)
286
+ raise EOFError
287
+ else:
288
+ fputs(u"Unknown command.\n", sys.stdout)
289
+ display_help()
290
+
291
+ global_message_counter += 1
292
+ continue
293
+ else:
294
+ content = user_input
295
+
296
+ # We do not allow content to be empty
297
+ if not content:
298
+ continue
299
+
300
+ return content
301
+
302
+
303
+ def main():
304
+ """Entry point for the chat interface."""
305
+ global global_message_counter
306
+
307
+ # Create the parser
308
+ parser = argparse.ArgumentParser()
309
+
310
+ # Add arguments
311
+ parser.add_argument('--api-key', type=str, required=False, help='API key')
312
+ parser.add_argument('--base-url', type=str, required=False, help='Base URL')
313
+ parser.add_argument('--model', type=str, required=False, help='Model name')
314
+ parser.add_argument('--load', metavar='JSONFILE', type=str, required=False, help='Load a conversation from JSONFILE')
315
+ parser.add_argument('--print', metavar='JSONFILE', type=str, required=False, help='Print a saved conversation from JSONFILE and exit')
316
+
317
+ # Parse arguments
318
+ args = parser.parse_args()
319
+
320
+ # Print a saved conversation from JSONFILE and exit
321
+ if args.print:
322
+ print_loaded_messages(load_messages_from_file(args.print))
323
+ else:
324
+ if (
325
+ not args.api_key
326
+ or not args.base_url
327
+ or not args.model
328
+ ):
329
+ parser.error('Must provide --api-key, --base-url, and --model when not printing a conversation using --print')
330
+
331
+ # Initialize messages
332
+ if args.load:
333
+ messages = load_messages_from_file(args.load)
334
+ else:
335
+ messages = []
336
+
337
+ # Non-interactive mode
338
+ if not IS_INTERACTIVE:
339
+ user_message_content = sys.stdin.read()
340
+
341
+ if user_message_content:
342
+ messages.append(
343
+ {
344
+ u'role': 'user',
345
+ u'content': user_message_content
346
+ }
347
+ )
348
+
349
+ for response in send_messages_to_model_and_stream_response(
350
+ args.api_key,
351
+ args.base_url,
352
+ args.model,
353
+ messages
354
+ ):
355
+ fputs(response, sys.stdout)
356
+
357
+ fputs(u'\n', sys.stdout)
358
+
359
+ # Interactive mode
360
+ else:
361
+ # Read readline history file
362
+ if readline is not None:
363
+ histfile = os.path.join(os.path.expanduser("~"), ".chat_history")
364
+ try:
365
+ readline.read_history_file(histfile)
366
+ except Exception:
367
+ pass
368
+
369
+ # Display greeting
370
+ fputs(
371
+ u'\nWelcome to Terminal Chat (Model: %s)\n' % args.model,
372
+ sys.stdout
373
+ )
374
+ display_help()
375
+
376
+ # Main loop
377
+ while True:
378
+ try:
379
+ user_message_content = get_single_message_content_from_user(
380
+ messages
381
+ )
382
+
383
+ messages.append(
384
+ {
385
+ u'role': 'user',
386
+ u'content': user_message_content
387
+ }
388
+ )
389
+ except EOFError:
390
+ break
391
+
392
+ try:
393
+ assistant_message_content = get_single_message_content_from_model(
394
+ args.api_key,
395
+ args.base_url,
396
+ args.model,
397
+ messages
398
+ )
399
+
400
+ messages.append(
401
+ {
402
+ u'role': u'assistant',
403
+ u'content': assistant_message_content
404
+ }
405
+ )
406
+ except URLError as e:
407
+ perror(e)
408
+
409
+ # Also remove the last user message
410
+ messages.pop()
411
+
412
+ # And skip incrementing global_message_counter
413
+ continue
414
+
415
+ global_message_counter += 1
416
+
417
+ # Write readline history file before exiting
418
+ if readline is not None:
419
+ readline.write_history_file(histfile)
420
+
421
+
422
+ if __name__ == "__main__":
423
+ main()
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "chatrepl"
7
+ version = "0.1.0-alpha"
8
+ description = "A Python 2.7+ REPL for interacting with LLMs with an OpenAI Chat Completions-compatible API."
9
+ readme = "README.md"
10
+ requires-python = ">=2"
11
+ license = "MIT"
12
+ authors = [
13
+ { name="Jifeng Wu", email="jifengwu2k@gmail.com" }
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 2",
17
+ "Programming Language :: Python :: 3",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+ dependencies = [
21
+ ]
22
+
23
+ [project.urls]
24
+ "Homepage" = "https://github.com/jifengwu2k/chatrepl"
25
+ "Bug Tracker" = "https://github.com/jifengwu2k/chatrepl/issues"
@@ -0,0 +1,7 @@
1
+ [bdist_wheel]
2
+ universal = 1
3
+
4
+ [egg_info]
5
+ tag_build =
6
+ tag_date = 0
7
+