erniecli.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. #!/usr/bin/python3
  2. import requests
  3. import json
  4. import sys
  5. import time
  6. import _thread
  7. import os
  8. import subprocess
  9. import socket
  10. ERNIE_API_KEY = ""
  11. ERNIE_SECRET_KEY = ""
  12. OPENAI_URL = ""
  13. OPENAI_API_KEY = ""
  14. OPENAI_MODEL = ""
  15. # 后端可选“openai”或“ernie”
  16. BACKEND = ""
  17. # 是否启用本地模式
  18. USE_LOCAL = False
  19. LOCAL_PORT = 11434
  20. PROMPT = """
  21. 你是一个Linux命令行专家,请将我的请求转换成一条可执行的bash命令。
  22. 只给一条命令,不要做任何解释或说明。
  23. 示例:
  24. 请求:显示系统版本信息。
  25. 输出:
  26. ```bash
  27. uname -a
  28. ```
  29. """
  30. # 设置API_KEY和SECRET_KEY
  31. def set_config():
  32. global ERNIE_API_KEY, ERNIE_SECRET_KEY, OPENAI_API_KEY, OPENAI_MODEL, OPENAI_URL, BACKEND
  33. if os.path.exists(os.path.expanduser('~/.config/erniecli/.ernierc')):
  34. get_config()
  35. elif not os.path.exists(os.path.expanduser('~/.config/erniecli')):
  36. os.makedirs(os.path.expanduser('~/.config/erniecli'))
  37. # 用黄色字体
  38. bidx = 0
  39. if BACKEND == "ernie":
  40. bidx = 1
  41. elif BACKEND == "openai":
  42. bidx = 2
  43. elif BACKEND == "":
  44. bidx = 0
  45. while True:
  46. choice_bidx = input("\033[1;33m请选择后端(0: None | 1: ernie | 2: openai)[current: {0}]:".format(bidx))
  47. if choice_bidx == '':
  48. bidx = bidx
  49. else:
  50. bidx = int(choice_bidx)
  51. if bidx == 1 or bidx == 2:
  52. break
  53. if bidx == 1:
  54. BACKEND = "ernie"
  55. elif bidx == 2:
  56. BACKEND = "openai"
  57. choose_local = input("\033[1;35m是否需要配置本地模式?(y/N)")
  58. if choose_local == "y":
  59. USE_LOCAL = True
  60. else:
  61. USE_LOCAL = False
  62. choice_ernie = input("\033[1;34m是否需要配置ernie?(y/N)")
  63. if choice_ernie == "y":
  64. apikey_value = input("\033[1;34m请输入API_KEY(当前值:"+ERNIE_API_KEY+"):")
  65. securekey_value = input("请输入SECRET_KEY(当前值"+ERNIE_SECRET_KEY+"):")
  66. if apikey_value != "":
  67. ERNIE_API_KEY = apikey_value.strip()
  68. if securekey_value != "":
  69. ERNIE_SECRET_KEY = securekey_value.strip()
  70. choice_openai = input("\033[1;36m是否需要配置openai?(y/N)")
  71. if choice_openai == "y":
  72. url_value = input("\033[1;36m请输入BASE URL(当前值:"+OPENAI_URL+"):")
  73. apikey_value = input("\033[1;36m请输入API_KEY(当前值:"+OPENAI_API_KEY+"):")
  74. model_value = input("请输入模型(当前值:"+OPENAI_MODEL+"):")
  75. if url_value != "":
  76. OPENAI_URL = url_value.strip()
  77. if apikey_value != "":
  78. OPENAI_API_KEY = apikey_value.strip()
  79. if model_value != "":
  80. OPENAI_MODEL = model_value.strip()
  81. with open(os.path.expanduser('~/.config/erniecli/.ernierc'), 'w', encoding='utf-8') as f:
  82. # 写入所有配置
  83. f.write("[GLOBAL]\n")
  84. f.write("BACKEND="+BACKEND+"\n")
  85. f.write("LOCAL="+str(USE_LOCAL)+"\n")
  86. f.write("\n[ERNIE]\n")
  87. f.write("API_KEY="+ERNIE_API_KEY+"\n")
  88. f.write("SECRET_KEY="+ERNIE_SECRET_KEY+"\n")
  89. f.write("\n[OPENAI]\n")
  90. f.write("URL="+OPENAI_URL+"\n")
  91. f.write("API_KEY="+OPENAI_API_KEY+"\n")
  92. f.write("MODEL="+OPENAI_MODEL+"\n")
  93. print("\033[1;32m配置成功\033[0m")
  94. sys.exit(0)
  95. # 读取$HOME/.config/erniecli/.ernierc文件中API_KEY和SECRET_KEY
  96. def get_config():
  97. global ERNIE_API_KEY, ERNIE_SECRET_KEY, OPENAI_API_KEY, OPENAI_MODEL, OPENAI_URL, BACKEND, USE_LOCAL
  98. config = os.path.expanduser('~/.config/erniecli/.ernierc')
  99. if not os.path.exists(config):
  100. print("\033[1;31m请进行使用erniecli进行配置\033[0m")
  101. sys.exit(0)
  102. # 读取配置文件,读取[global]的BACKEND
  103. group = "global"
  104. with open(config, 'r', encoding='utf-8') as f:
  105. for line in f.readlines():
  106. line = line.strip()
  107. if len(line) == 0 or line[0] == '#':
  108. continue
  109. elif line.startswith("["):
  110. group = line[1:-1]
  111. continue
  112. # 配置global
  113. if group == "GLOBAL":
  114. key, value = line.split('=')
  115. if key.strip() == "BACKEND":
  116. BACKEND = value.strip()
  117. if key.strip() == "LOCAL":
  118. USE_LOCAL = value.strip() == "True"
  119. if group == "ERNIE":
  120. key, value = line.split('=')
  121. if key.strip() == "API_KEY":
  122. ERNIE_API_KEY = value.strip()
  123. if key.strip() == "SECRET_KEY":
  124. ERNIE_SECRET_KEY = value.strip()
  125. if group == "OPENAI":
  126. key, value = line.split('=')
  127. if key.strip() == "API_KEY":
  128. OPENAI_API_KEY = value.strip()
  129. if key.strip() == "MODEL":
  130. OPENAI_MODEL = value.strip()
  131. if key.strip() == "URL":
  132. OPENAI_URL = value.strip()
  133. # 查询百度千帆
  134. def askERNIE(quest):
  135. url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=" + get_access_token()
  136. payload = json.dumps({
  137. "messages": [
  138. {
  139. "role": "user",
  140. "content": PROMPT+"我的问题是:{0}".format(quest)
  141. }
  142. ],
  143. "temperature": 0.95,
  144. "top_p": 0.8,
  145. "penalty_score": 1,
  146. "disable_search": False,
  147. "enable_citation": False,
  148. "response_format": "text"
  149. })
  150. headers = {
  151. 'Content-Type': 'application/json'
  152. }
  153. response = requests.request("POST", url, headers=headers, data=payload)
  154. return response.text
  155. def get_access_token():
  156. """
  157. 使用 AK,SK 生成鉴权签名(Access Token)
  158. :return: access_token,或是None(如果错误)
  159. """
  160. url = "https://aip.baidubce.com/oauth/2.0/token"
  161. params = {"grant_type": "client_credentials", "client_id": ERNIE_API_KEY, "client_secret": ERNIE_SECRET_KEY}
  162. return str(requests.post(url, params=params).json().get("access_token"))
  163. # 查询OpenAI接口,如赞同模型
  164. def askOpenAI(quest):
  165. global OPENAI_URL
  166. # 如果OPENAI_URL是/结尾,去掉最后的/
  167. if OPENAI_URL[-1] == '/':
  168. OPENAI_URL = OPENAI_URL[:-1]
  169. url = "{0}/v1/chat/completions".format(OPENAI_URL)
  170. payload = json.dumps({
  171. "model": OPENAI_MODEL,
  172. "messages": [
  173. {
  174. "role": "system",
  175. "content": PROMPT
  176. },
  177. {
  178. "role": "user",
  179. "content": quest
  180. }
  181. ],
  182. "temperature": 0.3
  183. })
  184. headers = {
  185. 'Content-Type': 'application/json',
  186. 'Authorization': 'Bearer '+OPENAI_API_KEY
  187. }
  188. response = requests.request("POST", url, headers=headers, data=payload)
  189. return response.text
  190. # 检查端口是否可用
  191. def check_port_available(port):
  192. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  193. sock.settimeout(1)
  194. result = sock.connect_ex(('localhost', port))
  195. if result == 0:
  196. return True
  197. else:
  198. return False
  199. # 查询本地
  200. def askLocal(quest):
  201. res = requests.post(
  202. "http://localhost:{0}/api/chat".format(LOCAL_PORT),
  203. json={
  204. "model": "qwen:7b", # 使用 mistral 模型
  205. "stream": False, # 禁用流式输出
  206. "messages": [
  207. {
  208. "role": "system",
  209. "content": PROMPT
  210. },
  211. {
  212. "role": "user",
  213. "content": quest
  214. }
  215. ],
  216. "temperature": 0.3
  217. },
  218. )
  219. return res.text
  220. def parse(answer, isLocal):
  221. answer = json.loads(answer)
  222. # 获取第一个结果
  223. if isLocal:
  224. result = answer['message']['content']
  225. else:
  226. if BACKEND=="ernie":
  227. result = answer['result']
  228. elif BACKEND=="openai":
  229. result = answer['choices'][0]['message']['content']
  230. lines = result.split('\n')
  231. # 获取lines中的```bash项到```项,并拼成一个字符串
  232. cmd = ''
  233. start = False
  234. for line in lines:
  235. if line.strip() == '```bash':
  236. start = True
  237. continue
  238. if start:
  239. if line.strip() == '```':
  240. # 去掉最后的\n
  241. cmd = cmd[:-1]
  242. break
  243. cmd += line+'\n'
  244. return cmd
  245. def loading(lock):
  246. chars = ['⣾', '⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽']
  247. i = 0
  248. print('')
  249. while lock[0]:
  250. i = (i+1) % len(chars)
  251. print('\033[A%s %s' %
  252. (chars[i], lock[1] or '' if len(lock) >= 2 else ''))
  253. time.sleep(0.1)
  254. # 执行alias命令,获取输出结果保存为alias_result
  255. def alias():
  256. # 定义一个dict
  257. alias_result = {'egrep': 'egrep --color=auto'
  258. ,'grep': 'grep --color=auto'
  259. ,'fgrep': 'fgrep --color=auto'
  260. ,'grep': 'grep --color=auto'
  261. ,'l': 'ls -CF'
  262. ,'ll': 'ls -alF'
  263. ,'la': 'ls -A'
  264. ,'ls': 'ls --color=auto'}
  265. return alias_result
  266. def replaceAlias(cmd):
  267. # 获取alias_result
  268. alias_result = alias()
  269. # 获取cmd中的第一个单词
  270. cmd_first = cmd.split(' ')[0]
  271. # 如果cmd_first在alias_result中,则替换
  272. if cmd_first in alias_result:
  273. cmd = alias_result[cmd_first] + ' ' + cmd.replace(cmd_first, '')
  274. return cmd
  275. if __name__ == '__main__':
  276. # 如果没有参数
  277. if len(sys.argv) < 2:
  278. print("Copyright (c) 2024 Xiongwei Yu. Info-Terminal Copilot v1.0 \n\n \
  279. Usage: \n \
  280. erniecli command question : \"?? question\" or \"? question\" for short, quest a command and run\n \
  281. erniecli config : set your config\n \
  282. erniecli alias : show alias\n \
  283. erniecli version : show version")
  284. sys.exit(0)
  285. # 获取第一个参数
  286. cmd = sys.argv[1]
  287. if cmd == "config":
  288. set_config()
  289. # 设置??别名
  290. if cmd == "alias":
  291. print ("alias erniecli='erniecli.py'")
  292. print ("alias ??='erniecli.py command'")
  293. print ("alias ??='erniecli.py command'")
  294. print ("alias ?='erniecli.py command'")
  295. print ("alias ?='erniecli.py command'")
  296. # 显示版本信息
  297. if cmd == "version":
  298. # 紫色显示
  299. print("\033[1;95m终端助理 Version 0.1\n\033[0m")
  300. # 用绿色字体显示“基于文心一言的对话式命令行助理”
  301. print("\033[1;32m基于大语言模型的对话式终端助理\n可使用百度千帆文心大模型ERNIE-3.5-8K或其他OpenAI接口的大语言模型\n让命令行插上AI的翅膀🪽\033[0m")
  302. sys.exit(0)
  303. # 如果cmd为command,调用ask函数
  304. if cmd == "command":
  305. get_config()
  306. # 获取第二个参数
  307. # 如果第二个参数为空,则输出错误,用红色字体显示
  308. if len(sys.argv) < 3:
  309. print("\033[1;31m请输入你的意图\033[0m")
  310. sys.exit(0)
  311. # 获取后面的所有参数,并拼接成字符串
  312. question = ' '.join(sys.argv[2:])
  313. # question = sys.argv[2]
  314. # 如果question为空,则输出错误,用红色字体显示
  315. if question == "":
  316. print("\033[1;31m请输入你的意图\033[0m")
  317. sys.exit(0)
  318. # 调用ask函数,并输出结果
  319. # 显示转圈
  320. lock = [True, '']
  321. try:
  322. _thread.start_new_thread(loading, (lock,))
  323. except Exception as e:
  324. print(e)
  325. #使用绿色字体
  326. cli = ""
  327. if USE_LOCAL:
  328. # 检查11434端口是否可用
  329. if check_port_available(LOCAL_PORT):
  330. # 本地ollama
  331. lock[1] ="\033[1;32m终端助理正在本地思考...\033[0m"
  332. answer = askLocal(question)
  333. cli = parse(answer, True)
  334. if cli== "":
  335. lock[1] ="\033[1;32m终端助理正在云端思考...\033[0m"
  336. # 百度千帆
  337. # answer = askERNIE(question)
  338. # cmd = parseERNIE(answer)
  339. if BACKEND=="ernie":
  340. query = askERNIE
  341. elif BACKEND=="openai":
  342. query = askOpenAI
  343. # OpenAI API
  344. answer = query(question)
  345. cli = parse(answer, False)
  346. lock[0] = False
  347. if cli == "":
  348. print("\033[1;31mAI没有找到可执行的命令\033[0m")
  349. sys.exit(0)
  350. print('\033[F\033[K',end = "\033[1;32m❯ \033[0m")
  351. print('\033[1;32m{0}\033[0m'.format(cli))
  352. while True:
  353. choice = input('\033[1;32m? \033[0m\033[1;90m是否执行 ⬆ ? [Y/n]\033[0m')
  354. if choice == 'Y' or choice == 'y' or choice == '':
  355. sys.stdout.write('\033[A\r')
  356. sys.stdout.flush()
  357. sys.stdout.write('\033[K')
  358. sys.stdout.flush()
  359. # 执行命令,并输出结果
  360. # print('')
  361. cmd = replaceAlias(cli)
  362. subprocess.run(cli, shell=True)
  363. # print('')
  364. # os.system(cmd)
  365. break
  366. elif choice == 'N' or choice == 'n':
  367. print('已取消')
  368. sys.exit(0)
  369. else:
  370. print("请输入正确的选项")