erniecli.py 13 KB

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