multiai 0.2__py3-none-any.whl
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.
- multiai/__init__.py +3 -0
- multiai/data/system.ini +28 -0
- multiai/entry.py +145 -0
- multiai/multiai.py +540 -0
- multiai/printlong.py +83 -0
- multiai-0.2.dist-info/LICENSE +21 -0
- multiai-0.2.dist-info/METADATA +137 -0
- multiai-0.2.dist-info/RECORD +11 -0
- multiai-0.2.dist-info/WHEEL +5 -0
- multiai-0.2.dist-info/entry_points.txt +2 -0
- multiai-0.2.dist-info/top_level.txt +1 -0
multiai/__init__.py
ADDED
multiai/data/system.ini
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[system]
|
|
2
|
+
version = 0.2
|
|
3
|
+
description = A Python library for text-based AI interactions
|
|
4
|
+
url = https://sekika.github.io/multiai/
|
|
5
|
+
|
|
6
|
+
[model]
|
|
7
|
+
ai_provider = openai
|
|
8
|
+
openai = gpt-4o-mini
|
|
9
|
+
anthropic = claude-3-haiku-20240307
|
|
10
|
+
google = gemini-1.5-flash
|
|
11
|
+
perplexity = llama-3.1-sonar-small-128k-chat
|
|
12
|
+
mistral = mistral-large-latest
|
|
13
|
+
|
|
14
|
+
[default]
|
|
15
|
+
temperature = 0.7
|
|
16
|
+
max_requests = 5
|
|
17
|
+
|
|
18
|
+
[command]
|
|
19
|
+
blank_lines = 0
|
|
20
|
+
always_copy = no
|
|
21
|
+
always_log = no
|
|
22
|
+
log_file = chat-ai-DATE.md
|
|
23
|
+
|
|
24
|
+
[prompt]
|
|
25
|
+
color = blue
|
|
26
|
+
english = If the following sentence is English, revise the text to improve its readability and clarity in English. If not, translate into English. No need to explain. Just output the result English text.
|
|
27
|
+
factual = Do not hallucinate. Do not make up factual information.
|
|
28
|
+
url = Summarize following text very briefly.
|
multiai/entry.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entry point of multiai
|
|
3
|
+
"""
|
|
4
|
+
import argparse
|
|
5
|
+
import configparser
|
|
6
|
+
import os
|
|
7
|
+
import readline
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import webbrowser
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from multiai import Prompt, Provider
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"entry",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def entry():
|
|
20
|
+
"""
|
|
21
|
+
Entry point of multiai
|
|
22
|
+
|
|
23
|
+
to be invoked with ai command
|
|
24
|
+
"""
|
|
25
|
+
client = Prompt()
|
|
26
|
+
# Load user setting from config file in the order of
|
|
27
|
+
# data/system.ini, ~/.multiai, .multiai
|
|
28
|
+
inifile = configparser.ConfigParser()
|
|
29
|
+
here = os.path.abspath(os.path.dirname(__file__))
|
|
30
|
+
inifile.read(os.path.join(here, 'data/system.ini'))
|
|
31
|
+
inifile.read(os.path.expanduser('~/.multiai'))
|
|
32
|
+
inifile.read('.multiai')
|
|
33
|
+
# Start reading [command] section of the config file
|
|
34
|
+
# blank_lines: blank lines required to finish input
|
|
35
|
+
client.blank_lines = inifile.getint('command', 'blank_lines')
|
|
36
|
+
# always_copy: always use -c option
|
|
37
|
+
always_copy = inifile.getboolean('command', 'always_copy')
|
|
38
|
+
# always_log: always use -l option
|
|
39
|
+
always_log = inifile.getboolean('command', 'always_copy')
|
|
40
|
+
# log_file: file name of the log file.
|
|
41
|
+
log_file = inifile.get('command', 'log_file')
|
|
42
|
+
log_file = os.path.expanduser(log_file)
|
|
43
|
+
log_file = log_file.replace('DATE', datetime.today().strftime('%Y%m%d'))
|
|
44
|
+
client.log_file = log_file
|
|
45
|
+
# user_agent: user agent when retrieving web data
|
|
46
|
+
client.user_agent = inifile.get('command', 'user_agent', fallback=None)
|
|
47
|
+
# [promot] section
|
|
48
|
+
prompt_english = inifile.get('prompt', 'english')
|
|
49
|
+
prompt_factual = inifile.get('prompt', 'factual')
|
|
50
|
+
prompt_url = inifile.get('prompt', 'url')
|
|
51
|
+
# Load commandline argument
|
|
52
|
+
parser = argparse.ArgumentParser(
|
|
53
|
+
description=f'multiai {client.version} - {client.description}')
|
|
54
|
+
parser.add_argument('prompt', nargs='*',
|
|
55
|
+
help='prompt for AI')
|
|
56
|
+
parser.add_argument('-d', '--document',
|
|
57
|
+
action='store_true', help='open document page and exit')
|
|
58
|
+
for provider in Provider:
|
|
59
|
+
name = provider.name.lower()
|
|
60
|
+
help = 'use ' + name
|
|
61
|
+
if client.ai_provider == provider:
|
|
62
|
+
help += ' (Default)'
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
'-' + name.replace('m', '')[0],
|
|
65
|
+
'--' + name,
|
|
66
|
+
action='store_true',
|
|
67
|
+
help=help)
|
|
68
|
+
parser.add_argument('-m', '--model',
|
|
69
|
+
help='set model')
|
|
70
|
+
parser.add_argument('-t', '--temperature',
|
|
71
|
+
help=f'set temperature. 0 is deterministic. Default is {client.temperature}.')
|
|
72
|
+
parser.add_argument('-e', '--english',
|
|
73
|
+
action='store_true', help='correct if English, translate into English otherwise')
|
|
74
|
+
parser.add_argument('-f', '--factual',
|
|
75
|
+
action='store_true', help='factual information')
|
|
76
|
+
parser.add_argument('-u', '--url',
|
|
77
|
+
help='retrieve text from the URL')
|
|
78
|
+
if not always_copy:
|
|
79
|
+
parser.add_argument('-c', '--copy',
|
|
80
|
+
action='store_true', help='copy the latest answer')
|
|
81
|
+
if not always_log:
|
|
82
|
+
parser.add_argument('-l', '--log',
|
|
83
|
+
action='store_true', help='save log file as chatgpt.md')
|
|
84
|
+
args = parser.parse_args()
|
|
85
|
+
# Get prompt
|
|
86
|
+
prompt = ' '.join(args.prompt)
|
|
87
|
+
# -d option
|
|
88
|
+
if args.document:
|
|
89
|
+
webbrowser.open(client.url)
|
|
90
|
+
sys.exit()
|
|
91
|
+
# Set ai_provider, ai_providers and model
|
|
92
|
+
client.ai_providers = []
|
|
93
|
+
for provider in Provider:
|
|
94
|
+
if getattr(args, provider.name.lower()):
|
|
95
|
+
client.ai_provider = provider
|
|
96
|
+
client.ai_providers.append(provider)
|
|
97
|
+
if len(client.ai_providers) == 0:
|
|
98
|
+
client.ai_providers = [client.ai_provider]
|
|
99
|
+
if len(client.ai_providers) == 1:
|
|
100
|
+
default_model = 'model_' + client.ai_provider.name.lower()
|
|
101
|
+
if args.model:
|
|
102
|
+
setattr(client, default_model, args.model)
|
|
103
|
+
client.model = getattr(client, default_model, None)
|
|
104
|
+
# -t option
|
|
105
|
+
if args.temperature:
|
|
106
|
+
try:
|
|
107
|
+
client.temperature = float(args.temperature)
|
|
108
|
+
except ValueError:
|
|
109
|
+
print("Invalid 'temperature': should be a number.")
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
if client.temperature < 0:
|
|
112
|
+
print("Invalid 'temperature': should be >=0.")
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
# -c option
|
|
115
|
+
if always_copy:
|
|
116
|
+
args.copy = True
|
|
117
|
+
client.copy = args.copy
|
|
118
|
+
# -l option
|
|
119
|
+
if always_log:
|
|
120
|
+
args.log = True
|
|
121
|
+
client.log = args.log
|
|
122
|
+
if args.log:
|
|
123
|
+
if not os.path.exists(log_file):
|
|
124
|
+
with open(log_file, 'w') as file:
|
|
125
|
+
file.write("# AI chat log\n\n")
|
|
126
|
+
# -e and -f option
|
|
127
|
+
pre_prompt = ''
|
|
128
|
+
if args.english:
|
|
129
|
+
pre_prompt = prompt_english + '\n\n'
|
|
130
|
+
if args.factual:
|
|
131
|
+
pre_prompt = prompt_factual + '\n'
|
|
132
|
+
# -u option
|
|
133
|
+
if args.url:
|
|
134
|
+
text = client.retrieve_from_url(args.url)
|
|
135
|
+
if prompt == '':
|
|
136
|
+
prompt = prompt_url
|
|
137
|
+
print(f'{client.color(client.role)}> {prompt}\n\nText of {args.url}')
|
|
138
|
+
prompt_summary = f'{prompt_url}\n\nText of {args.url}'
|
|
139
|
+
prompt += '\n' + text
|
|
140
|
+
client.ask_print(prompt, prompt_summary=prompt_summary)
|
|
141
|
+
# Finished loading arguments and run
|
|
142
|
+
if prompt == '' or args.url:
|
|
143
|
+
client.interactive(pre_prompt=pre_prompt)
|
|
144
|
+
else:
|
|
145
|
+
client.ask_print(pre_prompt + prompt)
|
multiai/multiai.py
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""
|
|
2
|
+
multiai - A Python library for text-based AI interactions
|
|
3
|
+
"""
|
|
4
|
+
import anthropic
|
|
5
|
+
import configparser
|
|
6
|
+
import enum
|
|
7
|
+
import google.generativeai as genai
|
|
8
|
+
import json
|
|
9
|
+
import openai
|
|
10
|
+
import os
|
|
11
|
+
import mistralai
|
|
12
|
+
import PyPDF2
|
|
13
|
+
import pyperclip
|
|
14
|
+
import requests
|
|
15
|
+
import sys
|
|
16
|
+
import trafilatura
|
|
17
|
+
from io import BytesIO
|
|
18
|
+
from multiai.printlong import print_long
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Prompt",
|
|
22
|
+
"Provider",
|
|
23
|
+
"ColorCode",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Prompt():
|
|
28
|
+
"""
|
|
29
|
+
The Prompt main application.
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
client = Prompt()
|
|
33
|
+
answer = client.ask(prompt)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
# Values independent of system or user setting file
|
|
38
|
+
self.role = 'user'
|
|
39
|
+
# Anthropic requires max_tokens, so default value is given.
|
|
40
|
+
# It can be overwritten by max_tokens.
|
|
41
|
+
self.max_tokens_anthropic = 4096
|
|
42
|
+
# Load system default values from data/system.ini
|
|
43
|
+
inifile = configparser.ConfigParser()
|
|
44
|
+
here = os.path.abspath(os.path.dirname(__file__))
|
|
45
|
+
inifile.read(os.path.join(here, 'data/system.ini'))
|
|
46
|
+
self.version = inifile.get('system', 'version')
|
|
47
|
+
self.description = inifile.get('system', 'description')
|
|
48
|
+
self.url = inifile.get('system', 'url')
|
|
49
|
+
# Load user setting from config file in the order of
|
|
50
|
+
# ~/.multiai, .multai
|
|
51
|
+
# It overwrites the system default values
|
|
52
|
+
conf_file = os.path.expanduser('~/.multiai')
|
|
53
|
+
inifile.read(conf_file)
|
|
54
|
+
inifile.read('.multiai')
|
|
55
|
+
self.set_provider(inifile.get('model', 'ai_provider'))
|
|
56
|
+
for provider in Provider:
|
|
57
|
+
name = provider.name.lower()
|
|
58
|
+
model = inifile.get('model', name, fallback=None)
|
|
59
|
+
if model is None:
|
|
60
|
+
print(f'multiai system error: {name} not found in [model].')
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
setattr(self, 'model_' + name, model)
|
|
63
|
+
self.temperature = inifile.getfloat('default', 'temperature')
|
|
64
|
+
self.max_requests = inifile.getint('default', 'max_requests')
|
|
65
|
+
prompt_color = inifile.get('prompt', 'color')
|
|
66
|
+
try:
|
|
67
|
+
self.prompt_color = ColorCode[prompt_color.upper()].value
|
|
68
|
+
except Exception:
|
|
69
|
+
print(f'Error in the settings file: color = {prompt_color}')
|
|
70
|
+
available_colors = [name.lower()
|
|
71
|
+
for name in ColorCode.__members__.keys()]
|
|
72
|
+
print(f'Available colors: {", ".join(available_colors)}')
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
# No system default value is given from here.
|
|
75
|
+
# Default values are given by fallback values.
|
|
76
|
+
self.max_tokens = inifile.getint(
|
|
77
|
+
'default', 'max_tokens', fallback=None)
|
|
78
|
+
for provider in Provider:
|
|
79
|
+
env = os.getenv(provider.name + '_API_KEY')
|
|
80
|
+
name = provider.name.lower()
|
|
81
|
+
key = name + '_api_key'
|
|
82
|
+
if env is None:
|
|
83
|
+
ini = inifile.get('api_key', name, fallback=None)
|
|
84
|
+
setattr(self, key, ini)
|
|
85
|
+
else:
|
|
86
|
+
setattr(self, key, env)
|
|
87
|
+
self.clear()
|
|
88
|
+
|
|
89
|
+
def set_provider(self, provider):
|
|
90
|
+
"""
|
|
91
|
+
Set AI provider
|
|
92
|
+
|
|
93
|
+
:param provider: str
|
|
94
|
+
AI provider (case insensitive)
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
self.ai_provider = Provider[provider.upper()]
|
|
98
|
+
except Exception:
|
|
99
|
+
print(f'AI provider "{provider}" is not available.')
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
def set_model(self, provider, model):
|
|
103
|
+
"""
|
|
104
|
+
Set model
|
|
105
|
+
|
|
106
|
+
:param prvider: str
|
|
107
|
+
AI provider (case insensitive)
|
|
108
|
+
:param model: str
|
|
109
|
+
AI model
|
|
110
|
+
"""
|
|
111
|
+
self.set_provider(provider)
|
|
112
|
+
self.model = model
|
|
113
|
+
setattr(self, 'model_' + provider.lower(), model)
|
|
114
|
+
|
|
115
|
+
def clear(self):
|
|
116
|
+
"""
|
|
117
|
+
Clear chat history.
|
|
118
|
+
"""
|
|
119
|
+
self.openai_messages = []
|
|
120
|
+
self.anthropic_messages = []
|
|
121
|
+
self.google_chat = None
|
|
122
|
+
self.perplexity_messages = []
|
|
123
|
+
self.mistral_messages = []
|
|
124
|
+
|
|
125
|
+
def ask(self, prompt, request=1, verbose=False):
|
|
126
|
+
"""
|
|
127
|
+
Ask a question to AI.
|
|
128
|
+
|
|
129
|
+
:param prompt: str
|
|
130
|
+
prompt to ask AI
|
|
131
|
+
:param request: int
|
|
132
|
+
numbers of repetitive request
|
|
133
|
+
:param verbose: boolean
|
|
134
|
+
show repeat process
|
|
135
|
+
:return: str
|
|
136
|
+
Answer from AI
|
|
137
|
+
"""
|
|
138
|
+
self.message = [
|
|
139
|
+
{
|
|
140
|
+
"role": self.role,
|
|
141
|
+
"content": prompt,
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
self.prompt = prompt
|
|
145
|
+
if request == 1:
|
|
146
|
+
self.prompt_continue = False
|
|
147
|
+
else:
|
|
148
|
+
self.prompt_continue = True
|
|
149
|
+
# For example, call ask_openai() for openai
|
|
150
|
+
func_name = 'ask_' + self.ai_provider.name.lower()
|
|
151
|
+
try:
|
|
152
|
+
func = getattr(self, func_name)
|
|
153
|
+
except AttributeError:
|
|
154
|
+
print(
|
|
155
|
+
f'multiai system error: {func_name}() function is not defined.')
|
|
156
|
+
sys.exit(1)
|
|
157
|
+
func()
|
|
158
|
+
# Error
|
|
159
|
+
if self.error:
|
|
160
|
+
return self.error_message
|
|
161
|
+
# Finish successfully
|
|
162
|
+
if self.finish_reason in ['stop', 'end_turn']:
|
|
163
|
+
return self.response
|
|
164
|
+
# Unexpected finish reason
|
|
165
|
+
if self.finish_reason not in ['length', 'max_tokens']:
|
|
166
|
+
self.response += '\n\nFinish reason: {self.finish_reason}'
|
|
167
|
+
return self.response
|
|
168
|
+
# Response not finished. Continue the request.
|
|
169
|
+
request += 1
|
|
170
|
+
if request > self.max_requests:
|
|
171
|
+
self.response += '\n\nFinished because of max_tokens and max_requests.'
|
|
172
|
+
return self.response
|
|
173
|
+
if verbose:
|
|
174
|
+
print(
|
|
175
|
+
f'{self.color("Repeating...")} max_requests = {self.max_requests}, requests = {request}\r',
|
|
176
|
+
end='')
|
|
177
|
+
response = self.response
|
|
178
|
+
answer = self.ask('continue', request=request, verbose=verbose)
|
|
179
|
+
if self.error:
|
|
180
|
+
return answer
|
|
181
|
+
return response + answer
|
|
182
|
+
|
|
183
|
+
def ask_print(self, prompt, prompt_summary=None):
|
|
184
|
+
"""
|
|
185
|
+
Ask a question to AI and print, copy, log
|
|
186
|
+
|
|
187
|
+
:param prompt: str
|
|
188
|
+
prompt to ask AI
|
|
189
|
+
:param prompt_summary: str
|
|
190
|
+
prompt shortened for logging
|
|
191
|
+
"""
|
|
192
|
+
print(f'{self.color("Please wait ......")}\r', end='')
|
|
193
|
+
if len(self.ai_providers) == 1:
|
|
194
|
+
answer = self.ask(prompt, verbose=True)
|
|
195
|
+
print(' ' * 50 + '\r', end='')
|
|
196
|
+
if self.error:
|
|
197
|
+
print(f'{self.color("Error message")}> {answer}')
|
|
198
|
+
sys.exit(1)
|
|
199
|
+
print(f'{self.color(self.model)}>')
|
|
200
|
+
if self.log:
|
|
201
|
+
if prompt_summary is not None:
|
|
202
|
+
prompt = prompt_summary
|
|
203
|
+
with open(self.log_file, mode='a') as f:
|
|
204
|
+
f.write(
|
|
205
|
+
f'### {self.role}:\n{prompt}\n### {self.model}:\n{answer}\n')
|
|
206
|
+
else:
|
|
207
|
+
answer = ''
|
|
208
|
+
if prompt_summary is None:
|
|
209
|
+
prompt_log = prompt
|
|
210
|
+
else:
|
|
211
|
+
prompt_log = prompt_summary
|
|
212
|
+
for provider in (self.ai_providers):
|
|
213
|
+
self.ai_provider = provider
|
|
214
|
+
single_answer = self.ask(prompt, verbose=True)
|
|
215
|
+
model = getattr(self, 'model_' + provider.name.lower(), None)
|
|
216
|
+
if self.error:
|
|
217
|
+
print(
|
|
218
|
+
f'{self.color("Error message from " + provider.name.lower())}> {single_answer}')
|
|
219
|
+
sys.exit(1)
|
|
220
|
+
answer += f'### {model}:\n{single_answer}\n\n'
|
|
221
|
+
answer = answer.strip()
|
|
222
|
+
if self.log:
|
|
223
|
+
with open(self.log_file, mode='a') as f:
|
|
224
|
+
f.write(
|
|
225
|
+
f'### {self.role}:\n{prompt_log}\n{answer}\n')
|
|
226
|
+
print(' ' * 50 + '\r', end='')
|
|
227
|
+
print_long(answer)
|
|
228
|
+
if self.copy:
|
|
229
|
+
pyperclip.copy(answer)
|
|
230
|
+
|
|
231
|
+
def interactive(self, pre_prompt=''):
|
|
232
|
+
"""
|
|
233
|
+
Interactive mode
|
|
234
|
+
|
|
235
|
+
:param pre_prompt: str
|
|
236
|
+
pre-prompt to append before prompt
|
|
237
|
+
"""
|
|
238
|
+
prompt = ''
|
|
239
|
+
blank = 0
|
|
240
|
+
b = self.blank_lines
|
|
241
|
+
if b > 0:
|
|
242
|
+
print(
|
|
243
|
+
f'\nInput {b} blank line{"s" if b > 1 else ""} to finish input.')
|
|
244
|
+
while True:
|
|
245
|
+
try:
|
|
246
|
+
if prompt == '':
|
|
247
|
+
line = input(f'{self.color(self.role)}> ')
|
|
248
|
+
else:
|
|
249
|
+
line = input()
|
|
250
|
+
except EOFError:
|
|
251
|
+
sys.exit()
|
|
252
|
+
except KeyboardInterrupt:
|
|
253
|
+
sys.exit()
|
|
254
|
+
if line == '':
|
|
255
|
+
if prompt == '':
|
|
256
|
+
print('Blank text entered. Enter "q" to quit.')
|
|
257
|
+
continue
|
|
258
|
+
blank += 1
|
|
259
|
+
if blank < self.blank_lines:
|
|
260
|
+
prompt += line + '\n'
|
|
261
|
+
else:
|
|
262
|
+
self.ask_print(pre_prompt + prompt.strip())
|
|
263
|
+
prompt = ''
|
|
264
|
+
blank = 0
|
|
265
|
+
elif prompt == '' and line in ['q', 'x', 'quit', 'exit']:
|
|
266
|
+
sys.exit()
|
|
267
|
+
else:
|
|
268
|
+
if self.blank_lines == 0:
|
|
269
|
+
self.ask_print(pre_prompt + line)
|
|
270
|
+
prompt = ''
|
|
271
|
+
else:
|
|
272
|
+
prompt += line + '\n'
|
|
273
|
+
blank = 0
|
|
274
|
+
|
|
275
|
+
def color(self, text):
|
|
276
|
+
"""
|
|
277
|
+
Return colored text with color defined at self.prompt_color
|
|
278
|
+
|
|
279
|
+
:param text: str
|
|
280
|
+
Text
|
|
281
|
+
:return: str
|
|
282
|
+
Colored text
|
|
283
|
+
"""
|
|
284
|
+
if sys.stdout.isatty():
|
|
285
|
+
return f'\033[{self.prompt_color}m{text}\033[0m'
|
|
286
|
+
else:
|
|
287
|
+
return text
|
|
288
|
+
|
|
289
|
+
def retrieve_from_url(self, url, verbose=True):
|
|
290
|
+
"""
|
|
291
|
+
Retrieve text from URL.
|
|
292
|
+
|
|
293
|
+
When URL ends with ".pdf", PDF file is converted to text.
|
|
294
|
+
|
|
295
|
+
:param url: str
|
|
296
|
+
URL to retrieve data from
|
|
297
|
+
:param verbose: boolean
|
|
298
|
+
whether to print message
|
|
299
|
+
:return: str
|
|
300
|
+
Retrieved text
|
|
301
|
+
"""
|
|
302
|
+
if verbose:
|
|
303
|
+
print('Retrieving ...\r', end='')
|
|
304
|
+
headers = {
|
|
305
|
+
'User-Agent': self.user_agent if hasattr(self, 'user_agent') else None}
|
|
306
|
+
try:
|
|
307
|
+
response = requests.get(url, headers=headers)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
if verbose:
|
|
310
|
+
print(e)
|
|
311
|
+
sys.exit(1)
|
|
312
|
+
if response.status_code != 200:
|
|
313
|
+
if verbose:
|
|
314
|
+
print(f'{response.status_code} - {response.reason}')
|
|
315
|
+
sys.exit(1)
|
|
316
|
+
if verbose:
|
|
317
|
+
print('Converting to text.\r', end='')
|
|
318
|
+
if url.lower().endswith('.pdf'):
|
|
319
|
+
with BytesIO(response.content) as pdf_file:
|
|
320
|
+
reader = PyPDF2.PdfReader(pdf_file)
|
|
321
|
+
text = ""
|
|
322
|
+
for page in range(len(reader.pages)):
|
|
323
|
+
text += reader.pages[page].extract_text()
|
|
324
|
+
else:
|
|
325
|
+
text = trafilatura.extract(response.text)
|
|
326
|
+
if text is None:
|
|
327
|
+
if verbose:
|
|
328
|
+
print(f'{url} could not be retrieved.')
|
|
329
|
+
sys.exit(1)
|
|
330
|
+
return text
|
|
331
|
+
|
|
332
|
+
# Implementations for each providers
|
|
333
|
+
def ask_openai(self):
|
|
334
|
+
"""
|
|
335
|
+
Ask a question to OpenAI.
|
|
336
|
+
"""
|
|
337
|
+
if self.openai_api_key is None:
|
|
338
|
+
self.error = True
|
|
339
|
+
self.error_message = 'API key for OpenAI is not set.'
|
|
340
|
+
return
|
|
341
|
+
openai.api_key = self.openai_api_key
|
|
342
|
+
if not self.prompt_continue:
|
|
343
|
+
self.openai_messages += self.message
|
|
344
|
+
try:
|
|
345
|
+
self.completion = openai.chat.completions.create(
|
|
346
|
+
messages=self.openai_messages,
|
|
347
|
+
model=self.model_openai,
|
|
348
|
+
temperature=self.temperature,
|
|
349
|
+
max_tokens=self.max_tokens
|
|
350
|
+
)
|
|
351
|
+
self.error = False
|
|
352
|
+
self.response = self.completion.choices[0].message.content.strip(
|
|
353
|
+
)
|
|
354
|
+
self.finish_reason = self.completion.choices[0].finish_reason
|
|
355
|
+
self.openai_messages += [{"role": "assistant",
|
|
356
|
+
"content": self.response}]
|
|
357
|
+
except openai.APIError as e:
|
|
358
|
+
self.error = True
|
|
359
|
+
try:
|
|
360
|
+
self.error_code = e.status_code
|
|
361
|
+
self.error_dict = e.body
|
|
362
|
+
self.error_type = f"Error {self.error_code}: {self.error_dict['code']}"
|
|
363
|
+
self.error_message = f"{self.error_type}\n{self.error_dict['message']}"
|
|
364
|
+
except Exception:
|
|
365
|
+
self.error_message = e
|
|
366
|
+
|
|
367
|
+
def ask_anthropic(self):
|
|
368
|
+
"""
|
|
369
|
+
Ask a question to Anthropic.
|
|
370
|
+
"""
|
|
371
|
+
if self.anthropic_api_key is None:
|
|
372
|
+
self.error = True
|
|
373
|
+
self.error_message = 'API key for Anthropic is not set.'
|
|
374
|
+
return
|
|
375
|
+
client = anthropic.Anthropic(api_key=self.anthropic_api_key)
|
|
376
|
+
self.anthropic_messages += self.message
|
|
377
|
+
try:
|
|
378
|
+
self.completion = client.messages.create(
|
|
379
|
+
messages=self.anthropic_messages,
|
|
380
|
+
model=self.model_anthropic,
|
|
381
|
+
temperature=self.temperature,
|
|
382
|
+
max_tokens=self.max_tokens if self.max_tokens else self.max_tokens_anthropic
|
|
383
|
+
)
|
|
384
|
+
self.error = False
|
|
385
|
+
self.response = self.completion.content[0].text.strip()
|
|
386
|
+
self.finish_reason = self.completion.stop_reason
|
|
387
|
+
self.anthropic_messages += [{"role": "assistant",
|
|
388
|
+
"content": self.response}]
|
|
389
|
+
except Exception as e:
|
|
390
|
+
self.error = True
|
|
391
|
+
try:
|
|
392
|
+
self.error_code = e.status_code
|
|
393
|
+
self.error_dict = e.body['error']
|
|
394
|
+
self.error_type = f"{self.error_code}: {self.error_dict['type']}"
|
|
395
|
+
self.error_message = f"{self.error_type}\n{self.error_dict['message']}"
|
|
396
|
+
except Exception:
|
|
397
|
+
self.error_message = e
|
|
398
|
+
|
|
399
|
+
def ask_google(self):
|
|
400
|
+
"""
|
|
401
|
+
Ask a question to Google.
|
|
402
|
+
"""
|
|
403
|
+
# Supress logging warnings of libraries
|
|
404
|
+
os.environ["GRPC_VERBOSITY"] = "ERROR"
|
|
405
|
+
os.environ["GLOG_minloglevel"] = "2"
|
|
406
|
+
if self.google_api_key is None:
|
|
407
|
+
self.error = True
|
|
408
|
+
self.error_message = 'API key for Google is not set.'
|
|
409
|
+
return
|
|
410
|
+
genai.configure(api_key=self.google_api_key)
|
|
411
|
+
model = genai.GenerativeModel(self.model_google)
|
|
412
|
+
if self.google_chat is None:
|
|
413
|
+
self.google_chat = model.start_chat(history=[])
|
|
414
|
+
config = genai.types.GenerationConfig(
|
|
415
|
+
temperature=self.temperature,
|
|
416
|
+
max_output_tokens=self.max_tokens)
|
|
417
|
+
try:
|
|
418
|
+
self.completion = self.google_chat.send_message(
|
|
419
|
+
self.prompt, generation_config=config)
|
|
420
|
+
self.error = False
|
|
421
|
+
self.response = self.completion.text.replace('•', '* ').strip()
|
|
422
|
+
self.finish_reason = self.completion.candidates[0].finish_reason.name.lower(
|
|
423
|
+
)
|
|
424
|
+
except Exception as e:
|
|
425
|
+
self.error = True
|
|
426
|
+
try:
|
|
427
|
+
self.error_message = e.message
|
|
428
|
+
except Exception:
|
|
429
|
+
self.error_message = e
|
|
430
|
+
|
|
431
|
+
def ask_perplexity(self):
|
|
432
|
+
"""
|
|
433
|
+
Ask a question to perplexity.
|
|
434
|
+
"""
|
|
435
|
+
if self.perplexity_api_key is None:
|
|
436
|
+
self.error = True
|
|
437
|
+
self.error_message = 'API key for Perplexity is not set.'
|
|
438
|
+
return
|
|
439
|
+
base_url = 'https://api.perplexity.ai'
|
|
440
|
+
client = openai.OpenAI(
|
|
441
|
+
api_key=self.perplexity_api_key,
|
|
442
|
+
base_url=base_url)
|
|
443
|
+
self.perplexity_messages += self.message
|
|
444
|
+
try:
|
|
445
|
+
self.completion = client.chat.completions.create(
|
|
446
|
+
messages=self.perplexity_messages,
|
|
447
|
+
model=self.model_perplexity,
|
|
448
|
+
temperature=self.temperature,
|
|
449
|
+
max_tokens=self.max_tokens
|
|
450
|
+
)
|
|
451
|
+
self.error = False
|
|
452
|
+
self.response = self.completion.choices[0].message.content.strip(
|
|
453
|
+
)
|
|
454
|
+
self.finish_reason = self.completion.choices[0].finish_reason
|
|
455
|
+
self.perplexity_messages += [{"role": "assistant",
|
|
456
|
+
"content": self.response}]
|
|
457
|
+
except openai.APIError as e:
|
|
458
|
+
self.error = True
|
|
459
|
+
try:
|
|
460
|
+
# print(f'e = {e.__dict__.keys()}')
|
|
461
|
+
# for key in e.__dict__.keys():
|
|
462
|
+
# print(f'e.{key} = {getattr(e, key)}')
|
|
463
|
+
message = trafilatura.extract(e.message)
|
|
464
|
+
self.error_message = message.splitlines()[0]
|
|
465
|
+
except Exception:
|
|
466
|
+
self.error_message = e
|
|
467
|
+
|
|
468
|
+
def ask_mistral(self):
|
|
469
|
+
"""
|
|
470
|
+
Ask a question to mistral.
|
|
471
|
+
"""
|
|
472
|
+
if self.mistral_api_key is None:
|
|
473
|
+
self.error = True
|
|
474
|
+
self.error_message = 'API key for Mistral is not set.'
|
|
475
|
+
return
|
|
476
|
+
client = mistralai.Mistral(api_key=self.mistral_api_key)
|
|
477
|
+
self.mistral_messages += self.message
|
|
478
|
+
try:
|
|
479
|
+
self.completion = client.chat.complete(
|
|
480
|
+
messages=self.mistral_messages,
|
|
481
|
+
model=self.model_mistral,
|
|
482
|
+
temperature=self.temperature,
|
|
483
|
+
max_tokens=self.max_tokens
|
|
484
|
+
)
|
|
485
|
+
self.error = False
|
|
486
|
+
self.response = self.completion.choices[0].message.content.strip(
|
|
487
|
+
)
|
|
488
|
+
self.finish_reason = self.completion.choices[0].finish_reason
|
|
489
|
+
self.mistral_messages += [{"role": "assistant",
|
|
490
|
+
"content": self.response}]
|
|
491
|
+
except mistralai.SDKError as e:
|
|
492
|
+
self.error = True
|
|
493
|
+
try:
|
|
494
|
+
self.error_code = e.status_code
|
|
495
|
+
self.error_dict = json.loads(e.body)
|
|
496
|
+
self.error_message = f"Error {self.error_code}: {self.error_dict['message']}"
|
|
497
|
+
except Exception:
|
|
498
|
+
self.error_message = e
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
class Provider(enum.Enum):
|
|
502
|
+
"""
|
|
503
|
+
Provider is an Enum representing AI provider available at multiai.
|
|
504
|
+
|
|
505
|
+
To add a provider definition,
|
|
506
|
+
(1) Add the provider here. Note that the first letter should not
|
|
507
|
+
overwrap other command-line options
|
|
508
|
+
(2) Define ask_provider() function in Prompt class
|
|
509
|
+
(3) Update clear() function in Prompt class
|
|
510
|
+
(4) Define default model at system.ini
|
|
511
|
+
"""
|
|
512
|
+
ANTHROPIC = enum.auto()
|
|
513
|
+
GOOGLE = enum.auto()
|
|
514
|
+
OPENAI = enum.auto()
|
|
515
|
+
PERPLEXITY = enum.auto()
|
|
516
|
+
MISTRAL = enum.auto()
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
class ColorCode(enum.Enum):
|
|
520
|
+
"""
|
|
521
|
+
ColorCode is an Enum representing ANSI color codes.
|
|
522
|
+
|
|
523
|
+
Each member of this Enum corresponds to a specific color used in terminal output.
|
|
524
|
+
"""
|
|
525
|
+
BLACK = 30
|
|
526
|
+
RED = 31
|
|
527
|
+
GREEN = 32
|
|
528
|
+
YELLOW = 33
|
|
529
|
+
BLUE = 34
|
|
530
|
+
MAGENTA = 35
|
|
531
|
+
CYAN = 36
|
|
532
|
+
WHITE = 37
|
|
533
|
+
BACK_BLACK = 40
|
|
534
|
+
BACK_RED = 41
|
|
535
|
+
BACK_GREEN = 42
|
|
536
|
+
BACK_YELLOW = 43
|
|
537
|
+
BACK_BLUE = 44
|
|
538
|
+
BACK_MAGENTA = 45
|
|
539
|
+
BACK_CYAN = 46
|
|
540
|
+
BACK_WHITE = 47
|
multiai/printlong.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
print_long - print long text with pypager
|
|
3
|
+
"""
|
|
4
|
+
import shutil
|
|
5
|
+
import unicodedata
|
|
6
|
+
import pypager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def print_long(text):
|
|
10
|
+
"""
|
|
11
|
+
Print long text with pypager.
|
|
12
|
+
|
|
13
|
+
When text fits in 1 page in a terminal, it uses print,
|
|
14
|
+
otherwise it uses pypager.
|
|
15
|
+
|
|
16
|
+
:param text: str
|
|
17
|
+
text to display
|
|
18
|
+
"""
|
|
19
|
+
default_terminal_size = (80, 20)
|
|
20
|
+
terminal_size = shutil.get_terminal_size(default_terminal_size)
|
|
21
|
+
lines_per_page = terminal_size.lines - 1
|
|
22
|
+
terminal_width = terminal_size.columns
|
|
23
|
+
wrapped_lines = []
|
|
24
|
+
for line in text.split('\n'):
|
|
25
|
+
wrapped_lines.extend(wrap_text(line, terminal_width))
|
|
26
|
+
total_lines = len(wrapped_lines)
|
|
27
|
+
wrapped_text = '\n'.join(wrapped_lines)
|
|
28
|
+
if total_lines <= lines_per_page:
|
|
29
|
+
print(text)
|
|
30
|
+
else:
|
|
31
|
+
p = pypager.pager.Pager()
|
|
32
|
+
p.add_source(pypager.source.StringSource(wrapped_text))
|
|
33
|
+
p.run()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def calculate_display_width(text):
|
|
37
|
+
"""
|
|
38
|
+
Calculate display width of a line.
|
|
39
|
+
|
|
40
|
+
:param text: str
|
|
41
|
+
text to count
|
|
42
|
+
"""
|
|
43
|
+
width = 0
|
|
44
|
+
for char in text:
|
|
45
|
+
if unicodedata.east_asian_width(char) in 'WF':
|
|
46
|
+
width += 2
|
|
47
|
+
else:
|
|
48
|
+
width += 1
|
|
49
|
+
return width
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def wrap_text(text, width):
|
|
53
|
+
"""
|
|
54
|
+
Wrap text of a line.
|
|
55
|
+
|
|
56
|
+
:param text: str
|
|
57
|
+
text to wrap
|
|
58
|
+
:param width: int
|
|
59
|
+
display width
|
|
60
|
+
"""
|
|
61
|
+
lines = []
|
|
62
|
+
current_line = ""
|
|
63
|
+
current_width = 0
|
|
64
|
+
for line in text.split('\n'):
|
|
65
|
+
if not line:
|
|
66
|
+
lines.append("")
|
|
67
|
+
continue
|
|
68
|
+
for char in line:
|
|
69
|
+
char_width = 2 if unicodedata.east_asian_width(char) in 'WF' else 1
|
|
70
|
+
if current_width + char_width > width:
|
|
71
|
+
lines.append(current_line)
|
|
72
|
+
current_line = char
|
|
73
|
+
current_width = char_width
|
|
74
|
+
else:
|
|
75
|
+
current_line += char
|
|
76
|
+
current_width += char_width
|
|
77
|
+
if current_line:
|
|
78
|
+
lines.append(current_line)
|
|
79
|
+
current_line = ""
|
|
80
|
+
current_width = 0
|
|
81
|
+
if current_line:
|
|
82
|
+
lines.append(current_line)
|
|
83
|
+
return lines
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Katsutoshi Seki (関 勝寿)
|
|
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,137 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: multiai
|
|
3
|
+
Version: 0.2
|
|
4
|
+
Summary: A Python library for text-based AI interactions
|
|
5
|
+
Home-page: https://sekika.github.io/multiai/
|
|
6
|
+
Author: Katsutoshi Seki
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: AI
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
22
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
23
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
|
|
24
|
+
Classifier: Operating System :: POSIX
|
|
25
|
+
Classifier: Operating System :: POSIX :: BSD
|
|
26
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
29
|
+
Classifier: Natural Language :: English
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Requires-Dist: openai
|
|
34
|
+
Requires-Dist: anthropic
|
|
35
|
+
Requires-Dist: google-generativeai
|
|
36
|
+
Requires-Dist: requests
|
|
37
|
+
Requires-Dist: mistralai
|
|
38
|
+
Requires-Dist: pypager
|
|
39
|
+
Requires-Dist: pyperclip
|
|
40
|
+
Requires-Dist: PyPDF2
|
|
41
|
+
Requires-Dist: trafilatura
|
|
42
|
+
|
|
43
|
+
# multiai
|
|
44
|
+
|
|
45
|
+
`multiai` is a Python library and command-line tool designed to interact with text-based generative AI models from the following providers:
|
|
46
|
+
|
|
47
|
+
| AI Provider | Web Service | Models Available |
|
|
48
|
+
|--------------|------------------------------------|----------------------------------------------------------------|
|
|
49
|
+
| **OpenAI** | [ChatGPT](https://chat.openai.com/) | [GPT Models](https://platform.openai.com/docs/models) |
|
|
50
|
+
| **Anthropic**| [Claude](https://claude.ai/) | [Claude Models](https://docs.anthropic.com/en/docs/about-claude/models) |
|
|
51
|
+
| **Google** | [Gemini](https://gemini.google.com/)| [Gemini Models](https://ai.google.dev/gemini-api/docs/models/gemini) |
|
|
52
|
+
| **Perplexity** | [Perplexity](https://www.perplexity.ai/) | [Perplexity Models](https://docs.perplexity.ai/docs/model-cards) |
|
|
53
|
+
| **Mistral** | [Mistral](https://chat.mistral.ai/chat) | [Mistral Models](https://docs.mistral.ai/getting-started/models/) |
|
|
54
|
+
|
|
55
|
+
## Key Features
|
|
56
|
+
|
|
57
|
+
- **Interactive Chat:** Communicate with AI directly from your terminal.
|
|
58
|
+
- **Multi-Line Input:** Supports multi-line prompts for complex queries.
|
|
59
|
+
- **Pager for Long Responses:** View lengthy responses conveniently using a pager.
|
|
60
|
+
- **Continuation Handling:** Automatically handle and request continuations if responses are cut off.
|
|
61
|
+
- **Automatic Chat Logging:** Automatically save your chat history for future reference.
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
Install `multiai`, then configure your API keys for your chosen AI providers as environment variables or in a user-setting file. Once that's done, you can start interacting with the AI.
|
|
66
|
+
|
|
67
|
+
- To send a simple query:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
ai hi
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
You should see a response like:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
gpt-4o-mini>
|
|
77
|
+
Hello! How can I assist you today?
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- For an interactive session, enter interactive mode:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
ai
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
In this mode, you can continue the conversation:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
user> hi
|
|
90
|
+
gpt-4o-mini>
|
|
91
|
+
Hello! How can I assist you today?
|
|
92
|
+
user> how are you
|
|
93
|
+
gpt-4o-mini>
|
|
94
|
+
I'm just a program, so I don't have feelings, but I'm here and ready to help you! How about you? How are you doing?
|
|
95
|
+
user>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
To see a list of all command-line options, use:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
ai -h
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For more detailed documentation, you can open the [manual](https://sekika.github.io/multiai/) in a web browser with:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
ai -d
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Using `multiai` as a Python Library
|
|
111
|
+
|
|
112
|
+
`multiai` can also be used as a Python library. Here’s a simple example:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import multiai
|
|
116
|
+
|
|
117
|
+
# Initialize the client
|
|
118
|
+
client = multiai.Prompt()
|
|
119
|
+
client.set_model('openai', 'gpt-4o') # Set model
|
|
120
|
+
client.temperature = 0.5 # Set temperature
|
|
121
|
+
|
|
122
|
+
# Send a prompt and get a response
|
|
123
|
+
answer = client.ask('hi')
|
|
124
|
+
print(answer)
|
|
125
|
+
|
|
126
|
+
# Continue the conversation with context
|
|
127
|
+
answer = client.ask('how are you')
|
|
128
|
+
print(answer)
|
|
129
|
+
|
|
130
|
+
# Clear the conversation context
|
|
131
|
+
client.clear()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The manual includes the following sample codes:
|
|
135
|
+
|
|
136
|
+
- A script that translates a text file into English.
|
|
137
|
+
- A local chat app that allows you to easily select from various AI models provided by different providers and engage in conversations with them.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
multiai/__init__.py,sha256=4-GlQ8EG6H-bo-MAtqRuEagzRVsbcTbYcypRg4QFb-o,87
|
|
2
|
+
multiai/entry.py,sha256=-HadVUo9TFs80moS1Z_-lwNbswuZAnbELF5xXjloHNg,5348
|
|
3
|
+
multiai/multiai.py,sha256=mOj5E1bHIuuE8B3aSE-4Nv410BovVTkuf25eBhOr274,18894
|
|
4
|
+
multiai/printlong.py,sha256=262_HUxTguI5oBjd7vbMdTqGQfub0ZX9KmNF_wwmPno,2085
|
|
5
|
+
multiai/data/system.ini,sha256=9kTZaOLY7SUdcGzeSlzE0BQfi-YqrMnoEr2V7YRoalA,785
|
|
6
|
+
multiai-0.2.dist-info/LICENSE,sha256=9KL7GR2OrW7sgnHc8IM48AAcQ6gTEstY3UQRpe3OC5A,1085
|
|
7
|
+
multiai-0.2.dist-info/METADATA,sha256=rJ5toCg-naptoJDAwx4C8ybKrcmSpfubMcuXA7SnPHI,4705
|
|
8
|
+
multiai-0.2.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
|
|
9
|
+
multiai-0.2.dist-info/entry_points.txt,sha256=gt2WpdEIKJEcBWGWyC0-bXGK0L7767qqSfu7NUEmr3Y,43
|
|
10
|
+
multiai-0.2.dist-info/top_level.txt,sha256=nIS7JbFZhSLFa-wpj8vjQ52SvxPEm9bAEYJxgV1gj68,8
|
|
11
|
+
multiai-0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
multiai
|