百度文心一言(ERNIE bot)API接入Android应用实践
Preface:
现在生成式AI越来越强大了,想在android上实现一个对话助手的功能,大概摸索了一下接入百度文心一言API的方法。
与AI助手交换信息的方式可以这么理解:
我向文心一言发送一个message:你好啊:
[
{
"role": "user",
"content": "你好啊"
}
]
文心一言回答我:你好,很高兴与你交流。请问你有什么具体的问题或需要帮助吗?我会尽力回答你的问题或与你对话:
{
"id":"as-n24a5sytuz",
"object":"chat.completion",
"created":1711203238,
"result":"你好,请问有什么我可以帮助你的吗?如果你有任何问题或需要帮助,请随时告诉我,我会尽力回答和提供帮助。",
"is_truncated":false,
"need_clear_history":false,
"finish_reason":"normal",
"usage":{
"prompt_tokens":1,
"completion_tokens":28,
"total_tokens":29
}
}
接着我继续发送message:今天是几号呢?......
[
{
"role": "user",
"content": "你好啊"
},
{
"role": "assistant",
"content": "你好,很高兴与你交流。请问你有什么具体的问题或需要帮助吗?我会尽力回答你的问题或与你对话。"
},
{
"role": "user",
"content": "今天是几号呢"
}
]
每一次发送message,都要带上之前的对话,这样才能实现连续对话的功能。
具体实现
在Android应用的AndroidManifest.xml
文件中添加网络访问权限:
<uses-permission android:name="android.permission.INTERNET" />
在build.gradle中添加必要的依赖:
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
接下来注册开发者账户、往里边充钱啥的,完成这些之后,在百度智能云控制台 (baidu.com)创建一个新应用,
如上图所示,我们需要API Key和Secret Key这俩东西
创建一个新的类以处理文心一言的API信息:WenXin.java,在Activity里需要实现文心一言的对话功能只需调用这个类就好了。
package com.example.wearespeakers;
import android.view.View;
import com.google.gson.Gson;
import okhttp3.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.*;
/**主要用于实现对接文心一言API的功能
*/
public class WenXin{
public static final String APP_ID = "56****59";//这个似乎还用不到
public static final String API_KEY = "oQtU**********wePzF";//填你自己应用的apikey
public static final String SECRET_KEY = "LxfNE*************W2UW0eX";//填你自己应用的secretkey
public JSONArray Dialogue_Content;//用来储存对话内容,当然初始是空的
WenXin(){
//构造函数,先初始化Dialogue_Content一下,此时里边是空的啥也没有
//不过也可以预先添加对话,以实现一些希望的业务功能
Dialogue_Content=new JSONArray();
}
static final OkHttpClient HTTP_CLIENT = new OkHttpClient().newBuilder().build();
public String GetAnswer(String user_msg) throws IOException, JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("role", "user");
jsonObject.put("content", user_msg);
// 将JSONObject添加到JSONArray中
//这里就是把用户说的话添加进对话内容里,然后发给文心一言
Dialogue_Content.put(jsonObject);
MediaType mediaType = MediaType.parse("application/json");
//这是一行参考代码,只能进行一次对话,要想多次对话就必须动态添加历史对话的内容
//RequestBody body = RequestBody.create(mediaType, "{\"messages\":[{\"role\":\"user\",\"content\":\"你好啊\"}],\"disable_search\":false,\"enable_citation\":false}");
RequestBody body = RequestBody.create(mediaType, "{\"messages\":" +
Dialogue_Content.toString() +
",\"disable_search\":false,\"enable_citation\":false}");
Request request = new Request.Builder()
.url("https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=" +
getAccessToken())
.method("POST", body)
.addHeader("Content-Type", "application/json")
.build();
Response response = HTTP_CLIENT.newCall(request).execute();
//解析出文心一言的回答
JSONObject json_feedback = new JSONObject(response.body().string());
//这里在开发的时候遇到了一个问题,注意response在上一行被取出里边的内容之后就自动关闭了,不能多次传参。
String re=json_feedback.getString("result");
//接下来把文心一言的回答加入到Dialogue_Content中
JSONObject jsontmp=new JSONObject();
jsontmp.put("assistant",re);
Dialogue_Content.put(jsontmp);
return re;
}
/**
* 从用户的AK,SK生成鉴权签名(Access Token)
*
* @return 鉴权签名(Access Token)
* @throws IOException IO异常
*/
public String getAccessToken() throws IOException, JSONException {
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "grant_type=client_credentials&client_id=" + API_KEY
+ "&client_secret=" + SECRET_KEY);
Request request = new Request.Builder()
.url("https://aip.baidubce.com/oauth/2.0/token")
.method("POST", body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.build();
Response response = HTTP_CLIENT.newCall(request).execute();
return new JSONObject(response.body().string()).getString("access_token");
}
}
在Activity中这样写的(Activity里的RecyclerView对话界面参考Android RecyclerView的使用(以实现一个简单的动态聊天界面为例)):
package com.example.wearespeakers;
import android.app.Activity;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static java.security.AccessController.getContext;
//此activity主要用来实现聊天界面
public class ChatActivity extends Activity {
private EditText et_chat;
private Button btn_send,btn_chat_return;
private ChatlistAdapter chatAdapter;
private List<Chatlist> mDatas;
private RecyclerView rc_chatlist;
final int MESSAGE_UPDATE_VIEW = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
init();
//聊天信息
mDatas = new ArrayList<Chatlist>();
Chatlist C1;
C1=new Chatlist("ABC:","Hello,world!");
mDatas.add(C1);
Chatlist C2;
C2=new Chatlist("DEF:","This is a new app.");
mDatas.add(C2);
//可以通过数据库插入数据
chatAdapter=new ChatlistAdapter(this,mDatas);
LinearLayoutManager layoutManager = new LinearLayoutManager(this );
rc_chatlist.setLayoutManager(layoutManager);
//如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
rc_chatlist.setHasFixedSize(true);
//创建并设置Adapter
rc_chatlist.setAdapter(chatAdapter);
//点击btn_send发送聊天信息
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//用户的提问
String user_ask=et_chat.getText().toString();//获取输入框里的信息
Chatlist C3;
C3=new Chatlist("User:",user_ask);
mDatas.add(C3);
chatAdapter.ResetChatlistAdapter(mDatas);
rc_chatlist.setAdapter(chatAdapter);
//文心一言的回答(以下才是用到WenXin.java的地方)
new Thread(new Runnable(){
@Override
public void run() {
//请求详情
Chatlist C4;
try {
WenXin wx=new WenXin();
C4=new Chatlist("WenXin:",wx.GetAnswer(user_ask));
} catch (IOException | JSONException e) {
throw new RuntimeException(e);
} finally {
}
mDatas.add(C4);
chatAdapter.ResetChatlistAdapter(mDatas);
Message msg = new Message();
msg.what = MESSAGE_UPDATE_VIEW;
ChatActivity.this.gHandler.sendMessage(msg);
}
}).start();
/*为什么要弄new Thread...这样呢?
因为像这种网络请求往往有延迟,需要新开一个进程去处理,与下面的gHandler相对应
当app收到来自文心一言的回答后,就去通知gHandler更新界面,把回答的段落显示出来
*/
}
});
//点击返回,返回mainActivity
btn_chat_return.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(ChatActivity.this,MainActivity.class);
startActivity(intent);
ChatActivity.this.finish();
}
});
}
private void init(){//执行一些初始化操作
btn_send=findViewById(R.id.btn_send);
et_chat=findViewById(R.id.et_chat);
btn_chat_return=findViewById(R.id.btn_chat_return);
rc_chatlist=(RecyclerView) findViewById(R.id.rc_chatlist);
}
public Handler gHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_UPDATE_VIEW) {
rc_chatlist.setAdapter(chatAdapter);//更新对话界面
}
}
};
}
直接把整个activity代码粘下来了,其实只需要关注new Thread和gHandler即可。
效果:
完整代码:https://gitee.com/liyizhe2002/we-are-speakers