基于Termux创建属于自己的随身AI工具集

基于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格式存储。读取这些配置文件可以使用如iniparserconfuse等工具。最简单的,使用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

config file
execution

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值