基于Termux创建属于自己的随身AI工具集
一、Termux简介及其使用环境
Termux是一款为Android设备设计的终端模拟器应用程序,它提供了一个Linux环境,让用户能够在Android设备上运行各种Linux软件和命令行工具。Termux不仅支持SSH和Telnet协议,还能够使用大多数的Linux软件包,包括Python、Node.js、Ruby等,这使得用户能够在移动设备上拥有类似于桌面Linux系统的体验。
二、Termux中使用Bash脚本的便利性
Bash(Bourne Again SHell)是一种广泛使用的默认命令行解释器。在Termux中,Bash脚本提供了一种快速、灵活且强大的自动化任务处理方式。用户可以通过编写脚本来批量执行命令,简化重复性操作,提高效率。此外,Bash脚本的可读性和可维护性也使得它们成为在Termux中进行自动化任务的理想选择。
三、AI大模型的API调用流程及bash中集成
AI大模型的API调用是实现智能功能的关键步骤,它涉及到多个环节,包括API接口申请、鉴权方式实现、数据组装、请求规则处理、返回数据处理以及回显展示等。以下是这些步骤的详细介绍:
3.1 API接口申请
在调用AI大模型的API之前,首先需要向服务提供商申请API接口的使用权限。这通常包括注册账号、创建应用、获取API密钥等步骤。在某些情况下,可能还需要通过审核才能获得高级功能的访问权限。
3.2 鉴权方式实现
API的鉴权是确保API安全访问的重要环节。常见的鉴权方式包括API密钥、OAuth、JWT(JSON Web Tokens)等。在Bash脚本中,可以通过环境变量或命令行参数传递API密钥,并在发送请求时将其包含在HTTP头部中。
3.3 数据组装实现
调用API时,需要将输入数据按照API要求的格式进行组装。这可能包括JSON、XML或其他格式。在Bash中,可以使用工具如jq来构建和修改JSON对象,或者使用其他文本处理工具来生成所需格式的数据。
3.4 请求规则处理
每个API都有自己的请求规则,包括请求方法(GET、POST等)、请求头部、请求体等。在Bash脚本中,需要根据这些规则来构造HTTP请求,并使用工具如curl或httpie来发送请求。
3.5 返回数据处理
AI大模型的API可能返回JSON对象或基于WebSocket的流式对象信息。在Bash中,可以使用jq来解析JSON对象,或者使用其他工具如xmlstarlet来处理XML数据。对于WebSocket流式数据,可能需要使用专门的WebSocket客户端库。
3.6 回显展示
最后,将处理后的数据以易于理解的方式展示给用户。在Bash脚本中,可以使用echo命令来打印文本信息,或者使用文本格式化工具来生成更复杂的输出。
在Bash中集成AI大模型的API调用是完全可行的。通过使用合适的命令行工具和脚本来处理HTTP请求、鉴权、数据组装和解析等任务,可以构建出强大的自动化脚本。这些脚本可以在Termux环境下运行,使得用户能够在移动设备上轻松地利用AI大模型的功能。
通过上述步骤,用户可以在Termux中创建一个高效且灵活的AI工具集,实现与AI大模型的无缝交互。这不仅能够提升工作效率,还能够在移动设备上实现复杂的数据处理任务。
四、Termux中信息输入输出的途径
在Termux中,信息的输入输出主要通过以下几种方式实现:
- 文字输入输出:可以通过标准的命令行界面进行,支持文本编辑和命令行操作。
- 语音输入输出:Termux支持通过外部应用如Google语音输入等进行语音输入,也可以用通用工具包termux-api中的termux-microphone-rec来进行音频录制,录制后最好进行音频统一转码操作,方便后续将语音转换成文本;而语音输出则可以通过安装TTS(文本到语音)软件包来实现,termux-api包中的termux-tts-speak也是个不错的推荐,缺陷是支持的发声角色较少,优点是不需要进行外部连接,速度较快。
arec:录制声音
if [[ -f $HOME/tmp/tmp.aac ]]; then
rm -f $HOME/tmp/tmp.aac
fi
if [[ -f $HOME/tmp/tmp.wav ]]; then
rm -f $HOME/tmp/tmp.wav
fi
termux-microphone-record -r 16000 -f $HOME/tmp/tmp.aac >/deu/null 2>&1
srec:停止录制声音
termux-microphone-record -q >/dev/null 2>&1
ffmpeg -hide_banner -i $HOME/tmp/tmp.aac -ar 16k -f wav -y $HOME/tmp/tmp.wav >/dev/null 2>&1
#mpv $HOME/tmp/tmp.wav
rrec:语音转文本(以paddlespeech服务为例,也可使用其它同等服务)
GetAudioContent() {
local strRet=""
# Get binary content of audio file
local strB64=""
local strPayload=""
strB64=`base64 $HOME/tmp/tmp.wav`
strB64="${strB64//$'\n'/}"
strPayload+="{"
strPayload+=" \"audio\": \"${strB64}\", "
strPayload+=" \"audio_format\": \"wav\", "
strPayload+=" \"sample_rate\": 16000, "
strPayload+=" \"lang\": \"zh_en\", "
strPayload+=" \"puc\": 1 "
strPayload+="}"
#echo "The base64 string of audio file is:"
#echo "$strB64"
# Get content via local asr server
local oPage=""
# Server with paddlespeech service
oPage=$(echo "${strPayload}"|curl -s -X POST -H "Content-Type: application/json" -d @- "http://xxx.xxx.com/paddlespeech/asr")
strRet=$(echo $oPage|jq '.result.transcription'|sed 's/\"//g')
echo ${strRet}
}
GetAudioContent
五、关键信息配置读取方法(conf/config.ini)
在Termux中,配置文件通常以.ini
、.conf
或.json
格式存储。读取这些配置文件可以使用如iniparser
、confuse
等工具。最简单的,使用xxx=xxxxx
这样的键值对进行内容存储,其简单读取的步骤如下:
config.ini
gpt_key=xxxxxx
bd_key=xxxxxxx
...
bash片断
for i in `cat $HOME/conf/ai_config.ini`
do
if [[ -n `echo "$i"|sed -n "/[^=]*=.*/p"` ]]; then
local strKey=`echo "$i"|sed "s/\([^=]*\)=.*/\1/"`
local strValue=`echo "$i"|sed "s/.*=\(.*\)/\1/"`
case "$strKey" in
"gpt_key")
strAPIKey_GPT=$strValue
;;
"bd_key")
strAPIKey_WXYY=$strValue
;;
*)
;;
esac
fi
done
六、临时文件处理方法(tmp/*)
在Termux中,临时文件通常存储在$HOME/tmp/
目录下。方便即用即删而不影响主体目录结构。
七、基于以上信息的Bash脚本执行流程
flowchart LR
Start --> Init_Env
subgraph InitEnv
direction TB
Work_Dir_Check --tmp and conf directory detect--> AI_Module_Select
AI_Module_Select --> Config_Load
Config_Load --> Init_API_And_Auth_Info
Init_API_And_Auth_Info --> Input_Mode_Select
end
Init_Env --- InitEnv
Init_Env --> Init_History
Init_History --> Create_Prompt
Create_Prompt --> Send_Quest_Info_To_Server
Send_Quest_Info_To_Server --> Get_AI_Answer
Get_AI_Answer --> Display_AI_Answer
subgraph DisplayAIAnswer
Show_Content_On_Screen --> Text_To_Speech
Text_To_Speech --> Save_Audio_In_Temp_Folder
Save_Audio_In_Temp_Folder --> Play_Audio_File
end
Display_AI_Answer --- DisplayAIAnswer
Display_AI_Answer --> Get_User_Input
Get_User_Input --Text or Audio record--> Input_Content_Read{Want to exit?}
Input_Content_Read --Yes--> Display_Info_And_Say_GoodBye
Input_Content_Read --No--> Update_History
subgraph UpdateHistory
Add_Current_Question --> Add_Current_Answer
Add_Current_Answer --> Detect_History_Length{Is overflow?}
Detect_History_Length --Yes--> Keep_Head_N_Messages
Keep_Head_N_Messages --> Delete_Center_Messages
Delete_Center_Messages --> Keep_Tail_Messages
Detect_History_Length --No--> End_UpdateHistory
end
Update_History --- UpdateHistory
Update_History --> Create_Prompt
Display_Info_And_Say_GoodBye --> End
八、示例代码及相关截图
#!$PREFIX/bin/bash
CUR_DIR="$(cd $(dirname $0) && pwd)"
declare -a ARR_TALK
HEADER=""
API_MODULE=""
TOKEN_WXYY=""
APPID_XFXH=""
APIKey_XFXH=""
APISecret_XFXH=""
APPID_TYQW=""
APPID_TYQW=""
APPCHANNEL_CLAUDE=""
APPTS_CLAUDE=""
APPMSG_PRE_CLAUDE=""
NO_LIMIT=1
CALL_BACK=0
INPUT_MODE=0
OLD_IFS=$IFS
IFS=$'\n'
pause() {
echo "press any key to contiune..."
read -n 1 -s
}
InitEnv() {
# Check temp folder
if [[ ! -d $HOME/tmp ]]; then
mkdir $HOME/tmp
fi
if [[ ! -d $HOME/conf ]]; then
mkdir $HOME/conf
fi
echo 请选择需要使用的模型[1-5]:
echo $'\t'1. OpenAI ChatGPT
echo $'\t'2. 百度 文心一言
echo $'\t'3. 讯飞 星火认知大模型
echo $'\t'4. 阿里 通义千问
echo $'\t'5. 清华 智谱AI
echo $'\t'6. Anthropic Claude
echo $'\t'7. 华为 盘古大模型
local nMode=0
read -p "请输入模型对应的序号,如:选百度的文心一言,请输入【2】:" nMode
# Set AI Module
case $nMode in
"1")
API_MODULE="GPT"
;;
"2")
API_MODULE="WXYY"
;;
"3")
API_MODULE="XFXH"
;;
"4")
API_MODULE="TYQW"
;;
"5")
API_MODULE="QHZP"
;;
"6")
API_MODULE="CLAUDE"
;;
"7")
API_MODULE="HWPG"
;;
*)
;;
esac
# Check config file
if [[ ! -f $HOME/conf/ai_config.ini ]]; then
echo Config file not exist...
pause
exit
fi
# Read parameters from config file
local strAPIKey_GPT=""
local strAPIKey_WXYY=""
local strAPISecret_WXYY=""
local strAPIKey_TYQW=""
local strAPISecret_TYQW=""
local strAPIKey_QHZP=""
local strAPISecret_QHZP=""
local strAPIKey_CLAUDE=""
for i in `cat $HOME/conf/ai_config.ini`
do
if [[ -n `echo "$i"|sed -n "/[^=]*=.*/p"` ]]; then
local strKey=`echo "$i"|sed "s/\([^=]*\)=.*/\1/"`
local strValue=`echo "$i"|sed "s/.*=\(.*\)/\1/"`
case "$strKey" in
"gpt_key")
strAPIKey_GPT=$strValue
;;
"bd_key")
strAPIKey_WXYY=$strValue
;;
"bd_secret")
strAPISecret_WXYY=$strValue
;;
"xf_id")
APPID_XFXH=$strValue
;;
"xf_secret")
APISecret_XFXH=$strValue
;;
"xf_key")
APIKey_XFXH=$strValue
;;
"qh_bigm")
strAPIKey_QHZP=$(echo $strValue|sed "s/\(.*\)\..*/\1/")
strAPISecret_QHZP=$(echo $strValue|sed "s/.*\.\(.*\)/\1/")
;;
"al_key")
strAPIKey_TYQW=$strValue
;;
"slack_key")
strAPIKey_CLAUDE=$strValue
;;
*)
;;
esac
else
continue
fi
done
# Initialize talk history
unset ARR_TALK
# Initialize api interface
local strAPIUrl_GPT=""
local strAPIUrl_WXYY=""
local strAPIUrl_XFXH=""
local strAPIUrl_TYQW=""
local strAPIUrl_QHZP=""
local strAPIUrl_CLAUDE=""
local strAPIUrl_HWPG=""
case "${API_MODULE}" in
"GPT")
echo "Current AI Module: OpenAI ChatGPT"
HEADER="Authorization: Bearer ${strAPIKey_GPT}"
#echo "API Key: ${HEADER}"
strAPIUrl_GPT="https://api.openai.com/v1/chat/completions"
APIUrl=$strAPIUrl_GPT
;;
"WXYY")
echo "Current AI Module: 百度 文心一言"
local strPageWXYY=$(curl -skL "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${strAPIKey_WXYY}&client_secret=${strAPISecret_WXYY}")
TOKEN_WXYY=$(echo ${strPageWXYY} | jq ".access_token" -r)
#strAPIUrl_WXYY="https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant?access_token=${TOKEN_WXYY}"
strAPIUrl_WXYY="https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro?access_token=${TOKEN_WXYY}"
APIUrl=$strAPIUrl_WXYY
;;
"XFXH")
echo "Current AI Module: 讯飞 星火模型"
#strAPIUrl_XFXH="wss://spark-api.xf-yun.com/v1.1/chat?host=spark-api.xf-yun.com"
#strAPIUrl_XFXH="wss://spark-api.xf-yun.com/v2.1/chat?host=spark-api.xf-yun.com"
#strAPIUrl_XFXH="wss://spark-api.xf-yun.com/v3.1/chat?host=spark-api.xf-yun.com"
strAPIUrl_XFXH="wss://spark-api.xf-yun.com/v3.5/chat?host=spark-api.xf-yun.com"
APIUrl=${strAPIUrl_XFXH}
;;
"TYQW")
echo "Current AI Module: 阿里 通义千问"
HEADER="Authorization: Bearer ${strAPIKey_TYQW}"
strAPIUrl_TYQW="https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"
APIUrl=${strAPIUrl_TYQW}
;;
"QHZP")
echo "Current AI Module: 清华 智谱AI"
#GetQHZPJWTInfo ${strAPIKey_QHZP} ${strAPISecret_QHZP}
#pause
strAuthQHZP=$(GetQHZPJWTInfo ${strAPIKey_QHZP} ${strAPISecret_QHZP})
HEADER="Authorization: Bearer ${strAuthQHZP}"
strAPIUrl_QHZP="https://open.bigmodel.cn/api/paas/v4/chat/completions"
APIUrl=${strAPIUrl_QHZP}
;;
"CLAUDE")
echo "Current AI Module: Anthropic Claude"
HEADER="Authorization: Bearer ${strAPIKey_CLAUDE}"
strAPIUrl_CLAUDE="https://cdtmehq.slack.com/api/chat.postMessage"
APIUrl=${strAPIUrl_CLAUDE}
APPCHANNEL_CLAUDE="C06072KKZA9"
APPMSG_PRE_CLAUDE="<@U054JKW0RK5> "
# 初始对话使用空消息ID产生一个新的会话序列,在此序列中进行对话操作
APPTS_CLAUDE=""
strDate=$(date +%Y%m%d%H%M%S)
ARR_TALK+=( "{ \"role\": \"user\", \"content\": \"自由对话场景_${strDate}_${RANDOM}\n你好。\" }" )
local strBody=$( CreatePrompt_CLAUDE )
#echo "Initilized param for claude is:"; echo "${strBody}"
#SetQuestion_For_Claue ${strBody}
#pause
local strRet=$( SetQuestion_For_Claue ${strBody} )
#echo "Initilized return: $strRet"
APPTS_CLAUDE=$( echo ${strRet} | jq -r ".ts" )
echo "Get current app ts number: ${APPTS_CLAUDE}"
;;
"HWPG")
echo "Current AI Module: 华为 盘古模型"
echo "To be contiuned..."
pause
exit
;;
*)
;;
esac
echo "Current API address: ${APIUrl}"
echo
echo "Please select input mode:"
read -p "[0:voice(default) | 1:text] " INPUT_MODE
if [[ -z ${INPUT_MODE} ]]; then
INPUT_MODE=0
fi
#echo "Pause in InitEnv()"
#pause
}
GetQHZPJWTInfo() {
local strKey=$1
local strSecret=$2
local strHeader='{"alg":"HS256","sign_type":"SIGN"}'
local strHeaderB64=$(echo -n $strHeader|base64 -w 0)
#echo "Header is : ${strHeader}"
#echo "Header base64 is : ${strHeaderB64}"
local nTimestamp=$(date +%s%3N)
local nExp=0
let nExp=nTimestamp+30*60*1000
local strPayload="{\"api_key\":\"$strKey\",\"exp\":$nExp,\"timestamp\":$nTimestamp}"
local strPayloadB64=$(echo -n $strPayload|base64 -w 0)
#echo "Payload is : ${strPayload}"
#echo "Payload base64 is : ${strPayloadB64}"
#local strHmac=$(echo -n "${strHeaderB64}.${strPayloadB64}" | openssl dgst -sha256 -hmac "$strSecret" -binary | base64 -w 0)
local strHmac=$(echo -n "${strHeaderB64}.${strPayloadB64}" | hmac256 --binary "$strSecret" | base64)
#echo "HMAC string is : ${strHmac}"
echo "${strHeaderB64}.${strPayloadB64}.${strHmac}"
}
GetContentText() {
local strRet=""
read -p "Press enter to start record"
arec
read -p "Press enter to stop record"
srec
strRet=$(rrec)
echo ${strRet}
}
# 文本转语音($1:转化内容, $2:是否本地)
# 提出为公共脚本 [2023/09/22]
ResetList() {
local nBufferLen=0
if [[ -z $1 ]]; then
nBufferLen=21
else
nBufferLen=$1
fi
# Detective minimized length, make sure bigger than 7
if [[ $nBufferLen -lt 7 ]]; then
$nBufferLen=7
fi
local nArrLen=${#ARR_TALK[@]}
if [[ $nArrLen -gt $nBufferLen ]]; then
local declare -a arrTmp
arrTmp=( ${ARR_TALK[@]:0:6} )
arrTmp+=( ${ARR_TALK[@]:$(( $nArrLen-($nBufferLen-6) )):$(( $nBufferLen-6 ))} )
unset ARR_TALK
ARR_TALK=(${arrTmp[@]})
fi
}
# ChatGPT发送数据组装
CreatePrompt_GPT() {
local strRet=""
local payload=""
payload+="{"
payload+="\"model\": \"gpt-3.5-turbo-16k\", "
payload+="\"temperature\": 0.7, "
payload+="\"max_tokens\": 1000, "
payload+="\"frequency_penalty\": 0, "
payload+="\"presence_penalty\": 0, "
payload+="\"stop\": [\"[DONE]\", \"User:\"], "
payload+="\"messages\": [ "
local nArrLen=${#ARR_TALK[@]}
for (( i=0; i<${nArrLen}; i++ ))
do
payload+=${ARR_TALK[$i]}
if [[ $i -lt $(( $nArrLen - 1 )) ]]; then
payload+=", "
fi
done
payload+="]}"
strRet=$payload
echo $strRet
#echo "Pause in CreatePrompt_GPT()"
#pause
}
# Claude发送数据组装 [2023/11/01]
CreatePrompt_CLAUDE() {
local strRet=""
local payload=""
local strText=$( echo ${ARR_TALK[-1]} | jq -r ".content" )
payload+="{"
payload+="\"channel\": \"${APPCHANNEL_CLAUDE}\", "
payload+="\"thread_ts\": \"${APPTS_CLAUDE}\", "
payload+="\"text\": \"${APPMSG_PRE_CLAUDE}${strText}\" "
payload+="}"
strRet=$payload
echo $strRet
#echo "Pause in CreatePrompt_CLAUDE()"
#pause
}
# 发送消息给Claude [2023/11/01]
SetQuestion_For_Claue() {
local strRet=""
local strParam=$1
local strCMD="curl -s -X POST -H \"${HEADER}\" -H \"Content-Type: application/json;charset=utf-8\" \"${APIUrl}\" -d \"${strParam//\"/\\\"}\""
#echo "Execution command:"$'\n'" ${strCMD}"
#pause
strRet=$( eval ${strCMD} )
echo $strRet
}
# 文心一言发送数据组装
CreatePrompt_WXYY() {
local strRet=""
local strPayload=""
strPayload+="{"
strPayload+=" \"temperature\": 0.7, "
strPayload+=" \"user_id\": \"37413911\", "
strPayload+=" \"stream\": false, "
strPayload+=" \"messages\": [ "
local nArrLen=${#ARR_TALK[@]}
for (( i=0; i<${nArrLen}; i++ ))
do
strPayload+=${ARR_TALK[$i]}
if [[ $i -lt $(( $nArrLen - 1 )) ]]; then
strPayload+=", "
fi
done
strPayload+=" ]"
strPayload+="}"
strRet=$strPayload
echo $strRet
#echo "Pause in CreatePrompt_WXYY()"
#pause
}
# 星火大模型发送数据组装
CreatePrompt_XFXH() {
local strRet=""
local strVer=""
if [[ -n $(echo $APIUrl|grep ".v2.1") ]]
then
strVer="v2"
elif [[ -n $(echo $APIUrl|grep ".v3.1") ]]
then
strVer="v3"
elif [[ -n $(echo $APIUrl|grep ".v3.5") ]]
then
strVer="v3.5"
fi
strRet+="{"
strRet+=" \"header\": {"
strRet+=" \"app_id\": \"${APPID_XFXH}\", "
strRet+=" \"uid\": \"0\""
strRet+=" }, "
strRet+=" \"parameter\": {"
strRet+=" \"chat\": {"
strRet+=" \"domain\": \"general${strVer}\", "
strRet+=" \"temperature\": 0.5, "
strRet+=" \"max_tokens\": 1024"
strRet+=" } "
strRet+=" },"
strRet+=" \"payload\": {"
strRet+=" \"message\": {"
strRet+=" \"text\": ["
local nArrLen=${#ARR_TALK[@]}
for (( i=0; i<${nArrLen}; i++ ))
do
strRet+=${ARR_TALK[$i]}
if [[ $i -lt $(( $nArrLen - 1 )) ]]; then
strRet+=", "
fi
done
strRet+=" ]"
strRet+=" }"
strRet+=" }"
strRet+="}"
echo $strRet
}
# 通义千问发送数据组装
CreatePrompt_TYQW() {
local strRet=""
local strPayload=""
strPayload+="{"
#strPayload+=" \"model\": \"qwen-7b-chat-v1\", "
strPayload+=" \"model\": \"qwen-turbo\", "
strPayload+=" \"parameters\": {\"top_p\": 0.7}, "
strPayload+=" \"input\": {"
local strPrompt=$(echo ${ARR_TALK[-1]}|jq '.content'|sed "s/\"//g")
strPayload+=" \"prompt\": \"${strPrompt}\", "
strPayload+=" \"history\": [ "
local nArrLen=${#ARR_TALK[@]}
for (( i=0; i<$(( ${nArrLen} - 1 )); i+=2 ))
do
local strUser=$(echo ${ARR_TALK[$i]} | jq '.content' | sed 's/\"//g')
local strBot=$(echo ${ARR_TALK[$(($i+1))]} | jq '.content' | sed 's/\"//g')
strPayload+=" {\"user\": \"${strUser}\", \"bot\": \"${strBot}\"}"
if [[ $(( $i+1 )) -lt $(( $nArrLen - 2 )) ]]; then
strPayload+=", "
fi
done
strPayload+=" ]"
strPayload+=" }"
strPayload+="}"
strRet=$strPayload
echo $strRet
#echo "Pause in CreatePrompt_TYQW()"
#pause
}
# 清华智谱发送数据组装
CreatePrompt_QHZP() {
local strRet=""
local strPayload=""
strPayload+="{"
strPayload+=" \"model\": \"glm-4\", "
strPayload+=" \"messages\": [ "
local nArrLen=${#ARR_TALK[@]}
for (( i=0; i<${nArrLen}; i++ ))
do
strPayload+=" $(echo ${ARR_TALK[$i]})"
if [[ $i -lt $(( $nArrLen - 1)) ]]; then
strPayload+=", "
fi
done
strPayload+=" ]"
strPayload+="}"
strRet=$strPayload
echo $strRet
#echo "Pause in CreatePrompt_QHZP()"
#pause
}
# AI请求组装
CreatePrompt() {
local strRet=""
case "${API_MODULE}" in
"GPT")
strRet=$( CreatePrompt_GPT )
;;
"WXYY")
strRet=$( CreatePrompt_WXYY )
;;
"XFXH")
strRet=$( CreatePrompt_XFXH )
;;
"TYQW")
strRet=$( CreatePrompt_TYQW )
;;
"QHZP")
strRet=$( CreatePrompt_QHZP )
;;
"CLAUDE")
strRet=$( CreatePrompt_CLAUDE )
;;
*)
;;
esac
echo $strRet
}
# 获取ChatGPT回复
GetResponseData_GPT() {
local strRet=""
local strMessage=""
#echo "Pause when in GetResponseData_GPT()"
#pause
strMessage="$*"
#echo "strMessage is : ${strMessage}"
if [[ ${CALL_BACK} -eq 0 ]]; then
local strCMD=""
#strCMD="echo ${strMessage} | jq '.content'"
#echo "Execution: $strCMD"
strRet=$( echo ${strMessage} | jq '.content' )
fi
echo $strRet
#echo "Pause in GetResponseData_GPT()"
#pause
}
# 获取文心一言回复
GetResponseData_WXYY() {
local strRet=""
local strMessage=""
#echo "Pause when in GetResponseData_WXYY()"
#pause
strMessage="$*"
#echo "strMessage is : ${strMessage}"
#local strCMD=""
#strCMD="echo ${strMessage} | jq '.result'"
#echo "Execution: $strCMD"
strRet=$( echo ${strMessage} | jq '.result' )
echo $strRet
#echo "Pause in GetResponseData_WXYY()"
#pause
}
# 获取讯飞星火回复
GetResponseData_XFXH() {
local strRet=""
local strMessage=""
#echo "Pause when in GetResponseData_XFXH()"
#pause
strMessage="$*"
strRet=${strMessage}
echo $strRet
#echo "Pause in GetResponseData_XFXH()"
#pause
}
# 获取通义千问回复
GetResponseData_TYQW() {
local strRet=""
local strMessage=""
#echo "Pause when in GetResponseData_TYQW()"
#pause
strMessage="$*"
#echo "strMessage is : ${strMessage}"
#local strCMD=""
#strCMD="echo ${strMessage} | jq '.result'"
#echo "Execution: $strCMD"
strRet=$( echo ${strMessage} | jq '.output.text' )
echo $strRet
#echo "Pause in GetResponseData_TYQW()"
#pause
}
# 获取清华智谱回复
GetResponseData_QHZP() {
local strRet=""
local strMessage=""
#echo "Pause when in GetResponseData_QHZP()"
#pause
strMessage="$*"
#echo "strMessage is : ${strMessage}"
#local strCMD=""
#strCMD="echo ${strMessage} | jq '.result'"
#echo "Execution: $strCMD"
strRet=$( echo ${strMessage} | jq '.content' )
echo $strRet
#echo "Pause in GetResponseData_QHZP()"
#pause
}
# 获取Claude回复 [2023/11/01]
GetResponseData_CLAUDE() {
local strResData="$*"
#echo "Get response data from claude:"; echo "${strResData}"
echo ${strResData}|jq -r ".text"
}
# 获取AI回复
GetResponseData() {
local strRet=""
local strMessage=""
#echo "Pause when in GetResponseData()"
#pause
strMessage="$*"
#echo "strMessage is : ${strMessage}"
local oMessage=""
if [[ ! -z "${strMessage}" ]]; then
case "${API_MODULE}" in
"GPT")
oMessage=$( echo ${strMessage} | jq '.choices[0].message' )
#echo "oMessage is :"
#echo ${oMessage} | jq '.'
strRet=$( GetResponseData_GPT ${oMessage} )
;;
"WXYY")
strRet=$( GetResponseData_WXYY ${strMessage} )
;;
"XFXH")
strRet=$( GetResponseData_XFXH ${strMessage} )
;;
"TYQW")
strRet=$( GetResponseData_TYQW ${strMessage} )
;;
"QHZP")
oMessage=$( echo ${strMessage} | jq '.choices[0].message' )
#echo "oMessage is :"
#echo ${oMessage} | jq '.'
strRet=$( GetResponseData_QHZP ${oMessage} )
;;
"CLAUDE")
strRet=$( GetResponseData_CLAUDE ${strMessage} )
;;
*)
;;
esac
fi
echo ${strRet//\"/}
#echo "Pause in GetResponseData()"
#pause
}
# 获取讯飞鉴权参数($1:鉴权服务器, $2:API接口)
GetXunFeiAuth() {
local strDate=$(date -uR|sed 's/\+0000/GMT/')
#echo strDate : ${strDate}
local strAuthPre=""
strAuthPre+="host: $1\n"
strAuthPre+="date: ${strDate}\n"
strAuthPre+="GET $2 HTTP/1.1"
#echo "strAuthPre :"
#echo -e "${strAuthPre}"
local strB64=$(echo -ne ${strAuthPre}|hmac256 --binary ${APISecret_XFXH}|base64)
#echo strB64 : ${strB64}
local strAuthOrigin="api_key=\"${APIKey_XFXH}\", algorithm=\"hmac-sha256\", headers=\"host date request-line\", signature=\"${strB64}\""
#echo strAuthOrigin : ${strAuthOrigin}
local strAuthOriginB64=""
for i in $(echo -n ${strAuthOrigin}|base64)
do
strAuthOriginB64+=$i
done
#echo strAuthOriginB64 : ${strAuthOriginB64}
local strParam="authorization=${strAuthOriginB64}&date=$(echo $strDate|sed 's/ /+/g;s/:/%3A/g;s/,/%2C/g')"
echo "$strParam"
}
GetAIAnswer() {
local oRet=""
local strBody=""
strBody="$*"
#echo "Requst data : ${strBody}"
#echo "Pause after read body"
#pause
local strCMD=""
case "${API_MODULE}" in
"GPT")
strCMD="curl -s -X POST -H \"Content-Type: application/json\" -H \"${HEADER}\" -d '${strBody}' \"${APIUrl}\""
#echo "Execution : $strCMD"
oRet=$( eval $strCMD )
;;
"WXYY")
strCMD="curl -s -X POST -H \"Content-Type: application/json\" -d '${strBody}' \"${APIUrl}\""
#echo "Execution : $strCMD"
oRet=$( eval $strCMD )
;;
"XFXH")
local strVer=""
if [[ -n $(echo $APIUrl|grep ".v1.1") ]]; then
strVer="v1.curl -X POST -H "Content-Type: application/json;charset=utf-8" -H "${HEADER}" "https://cdtmehq.slack.com/api/conversations.replies" -d "${strBodyQuery}" 1"
elif [[ -n $(echo $APIUrl|grep ".v2.1") ]]; then
strVer="v2.1"
elif [[ -n $(echo $APIUrl|grep ".v3.1") ]]; then
strVer="v3.1"
elif [[ -n $(echo $APIUrl|grep ".v3.5") ]]; then
strVer="v3.5"
fi
local strParam=$( GetXunFeiAuth "spark-api.xf-yun.com" "/${strVer}/chat" )
strCMD="echo \"${strBody}\" | websocat \"${APIUrl}&${strParam}\""
#echo "Execution : $strCMD"
#echo "${strBody}" | websocat "${APIUrl}&${strParam}"
local strSeg=""
for i in $(echo "${strBody}" | websocat "${APIUrl}&${strParam}")
do
strSeg="$(echo "$i"|jq ".payload.choices.text[0].content"|sed "s/\"//g")"
if [[ -n ${strSeg} ]]; then
oRet+=${strSeg}
fi
done
#echo $oRet
#pause
;;
"TYQW")
strCMD="curl -s -X POST -H \"Content-Type: application/json\" -H \"${HEADER}\" -d '${strBody}' \"${APIUrl}\""
#echo "Execution : $strCMD"
oRet=$( eval $strCMD )
;;
"QHZP")
strCMD="curl -s -X POST -H \"Content-Type: application/json;charset=utf-8\" -H \"${HEADER}\" -d '${strBody}' \"${APIUrl}\""
#echo "Execution : $strCMD"
oRet=$( eval $strCMD )
;;
"CLAUDE")
local strRetSend=$( SetQuestion_For_Claue ${strBody} )
sleep 1
#GetRetMsg_Claude $strRetSend
oRet=$( GetRetMsg_Claude $strRetSend )
;;
*)
;;
esac
echo $oRet
#echo "Pause in GetAIAnswer()"
#pause
}
# Claude查询消息回复 [2023/11/01]
GetRetMsg_Claude() {
local strRet=""
local strInput="$*"
#echo "The input string is:"; echo "${strInput}"
#pause
local strBodyQuery=""
strBodyQuery="channel=${APPCHANNEL_CLAUDE}"
strBodyQuery+="&ts=${APPTS_CLAUDE}"
strBodyQuery+="&limit=2"
#echo "Query params is:"; echo "${strBodyQuery}"
#pause
local strLastTS=$( echo ${strInput}|jq -r ".ts" )
#echo "Last ts is: ${strLastTS}"
#pause
local strCMD="curl -s -X GET -H \"${HEADER}\" \"https://cdtmehq.slack.com/api/conversations.replies?${strBodyQuery}\" "
#echo "Execution command:"; echo "${strCMD}"
#pause
local strRetRecive=$( eval $strCMD )
#echo "Return data:"; echo "${strRetRecive}"
#pause
local strRetTS=$( echo $strRetRecive|jq -r ".messages[-1].ts" )
#echo "Return ts is: ${strRetTS}"
#pause
if [[ $strLastTS == $strRetTS ]]; then
GetRetMsg_Claude ${strInput}
return
fi
sleep 0.3
if [[ -n $(echo ${strRetRecive}|jq -r ".messages[-1].text" | grep "gt; _\*Please note") ]];then
strRet=$( echo ${strRetRecive}|jq -r ".messages[-2]" )
else
strRet=$( echo ${strRetRecive}|jq -r ".messages[-1]" )
fi
if [[ -n $(echo ${strRet}|jq ".text"|grep "_Typing") ]];then
GetRetMsg_Claude ${strInput}
return
fi
echo $strRet
}
main() {
InitEnv
# 初始对话
local roleUser="学生"
local roleAIer="医学院老师"
local virEnv="医学机能实验室"
local strSysPrompt1=""
local strSysPrompt2=""
local userInput1=""
local userInput2=""
if [[ ${NO_LIMIT} -eq 0 ]]; then
strSysPrompt1="你是一个有用的智能医学助手。这里有一份对话资料,里面记录了一些医学机能实验的相关资料,详情如下:"
strSysPrompt2=$( cat $HOME/conf/BGStory-JN.txt )
ARR_TALK+=( "{ \"role\": \"user\", \"content\": \"${strSysPrompt1}\n${strSysPrompt2}\" }" )
if [[ ${API_MODULE} == "CLAUDE" ]]; then
strBodySend=$( CreatePrompt )
strRetSend=$( SetQuestion_For_Claue $strBodySend )
sleep 2
fi
ARR_TALK+=( "{ \"role\": \"assistant\", \"content\": \"好的。\" }" )
userInput1="后面的对话将基于一个虚拟的${virEnv}中,你将扮演一个${roleAIer}的角色,你的名字叫『XX医学小助手』,你知道所有的医学专业知识,并知道资料中${roleAIer}所知道的所有知识点,你将以${roleAIer}所使用的语气、方式和词汇来进行交流,用户将扮演${roleUser}与你进行对话,若有提问符合资料中的情况,以资料中的回答为主。虚拟场景中请忽略掉你是AI语音助手的事实,不要作任何解释。在回答中,请不要重复${roleUser}所提的问题,直接对问题作答。但始终要记住你不是在真实应用场景,而是在虚拟对话场景中,你扮演着${roleAIer}角色,会一直以${roleAIer}的身份进行交谈。另外,如果${roleUser}用英文向你提问,请以英文回答。"
#同时请根据对角色的理解,对一些明显偏离此角色知识范畴(机能和医学相关)的问题可以选择符合角色语气的回绝。
userInput2="回答问题时,请尽量简短作答,用户明确需要提供详细解释时除外。"
#如${roleUser}提问符合资料中的问题,请以function方式进行回复,并将所属问题和回复答案作为参数发送给回调函数。
ARR_TALK+=( "{ \"role\": \"user\", \"content\": \"${userInput1}\n${userInput2}\" }" )
if [[ ${API_MODULE} == "CLAUDE" ]]; then
strBodySend=$( CreatePrompt )
strRetSend=$( SetQuestion_For_Claue $strBodySend )
sleep 2
fi
ARR_TALK+=( "{ \"role\": \"assistant\", \"content\": \"好的。\" }" )
userInput2="如已理解需求。请回答『你好,欢迎来到${virEnv}。』。"
else
strSysPrompt1="你是一个有用的小助手,我很感谢你给我带来的帮助。"
ARR_TALK+=( "{ \"role\": \"user\", \"content\": \"${strSysPrompt1}\" }" )
if [[ ${API_MODULE} == "CLAUDE" ]]; then
strBodySend=$( CreatePrompt )
strRetSend=$( SetQuestion_For_Claue $strBodySend )
sleep 2
fi
ARR_TALK+=( "{ \"role\": \"assistant\", \"content\": \"好的。我是XX医学小助手。\" }" )
userInput2="你好。很高兴认识你。"
fi
ARR_TALK+=( "{ \"role\": \"user\", \"content\": \"${userInput2}\" }" )
#echo "初始对话设置:"
#declare -p ARR_TALK
#echo "${ARR_TALK[@]}"
#pause
local strBody=$( CreatePrompt )
#CreatePrompt
#echo "Send body : "
#echo "${strBody}" | jq "."
#echo "Pause before send body"
#pause
local oPage=""
#GetAIAnswer ${strBody}
oPage=$( GetAIAnswer ${strBody} )
#echo "Return response from API is :"
#echo ${oPage} | jq "."
#echo "Pause after GetAIAnswer()"
#pause
local userInput=${userInput2}
local nIndex=1
local AIResponse=""
# 开始多轮对话
for (( ; ; ))
do
let nIndex+=1
AIResponse=$( GetResponseData ${oPage} )
AIResponse="${AIResponse//\\n\\n/$'\n '}"
AIResponse="${AIResponse//\\n/$'\n '}"
#echo "Get result of response: ${AIResponse}"
#echo "Pause after get response"
#pause
if [[ -z "${AIResponse}" ]]; then
echo "无效对话记录,请重试..."
else
echo "ME: ${userInput}"
echo "AI: ${AIResponse}"
for iText in $(echo -e "${AIResponse}")
do
tts "${iText// /}"
done
fi
if [[ ${INPUT_MODE} -eq 0 ]]; then
userInput=$( GetContentText )
else
read -p "Input your question:" userInput
fi
if [[ "$userInput" == "退出" || "$userInput" == "Quit" ]]; then
break
fi
ResetList
# 清空回复历史,避免空值干扰
oPage=""
if [[ -n "$AIResponse" ]]; then
ARR_TALK+=( "{ \"role\": \"assistant\", \"content\": \"${AIResponse}\" }" )
fi
if [[ -n "$userInput" ]]; then
ARR_TALK+=( "{ \"role\": \"user\", \"content\": \"${userInput}\" }" )
strBody=$( CreatePrompt )
echo "${strBody}" | jq '.'
#GetAIAnswer ${strBody}
oPage=$( GetAIAnswer ${strBody} )
fi
done
tts "谢谢使用,期待下次与您见面。"
echo "对话结束,感谢您的使用。"
}
main