目前正在着手开发一款功能全面的接口自动化测试平台,该平台将涵盖登录、用户管理、权限控制、项目管理、用例管理、测试数据管理、测试报告生成、任务调度、缺陷跟踪以及系统配置等多个核心模块。随着智能体的爆火,准备开发一款测试工具智能助手,可以帮助执行任务、生成数据、分析测试结果等。因为智能助手的使用需要依赖平台的数据,所以本篇内容主要讲智能助手的搭建过程。具体的智能助手的操作逻辑,需要等平台主要功能完成后实现。
技术选型
- 后端:采用Java作为主要开发语言。
- 前端:使用VUE框架进行界面展示。
- 智能助手:使用
DeepSeek
的Function Calling
,将函数传送给AI大模型,AI判断是否需要使用函数执行。
定义实体
DeepSeek
的Function Calling
,在调用模型接口时,需要将自定义的函数,以List
方式传给模型,然后模型返回用户的消息是否需要使用Function Calling。函数列表的格式如下:
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather of an location, the user shoud supply a location first",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
}
},
"required": ["location"]
},
}
},
]
我们根据上面的格式,定义自己的实体类:
@Data
public class Tool {
String type;
Function function;
}
@Data
public class Function {
String name;
String description;
Parameters parameters;
}
@Data
public class Parameters {
String type;
Properties properties;
List<String> required;
}
@Data
public class Properties {
Location location;
}
@Data
public class Location {
String type;
String description;
}
定义回调函数格式
public class Tools {
private static Tool createTool(ToolParameter toolParameterDTO) {
Location location = new Location();
location.setDescription(toolParameterDTO.getLocalDesc());
location.setType("string");
Properties properties = new Properties();
properties.setLocation(location);
Parameters parameters = new Parameters();
parameters.setType("object");
parameters.setProperties(properties);
parameters.setRequired(toolParameterDTO.getRequired());
Function function = new Function();
function.setName(toolParameterDTO.getName());
function.setDescription(toolParameterDTO.getFuncDesc());
function.setParameters(parameters);
Tool tool = new Tool();
tool.setType("function");
tool.setFunction(function);
return tool;
}
private static Tool runTestJob() {
ToolParameter runTestJobTool = new ToolParameter();
runTestJobTool.setName("runTestJob");
runTestJobTool.setFuncDesc("执行接口测试任务,运行接口测试任务");
runTestJobTool.setLocalDesc("任务名称,例如:订单模块测试任务、用户中心测试任务");
runTestJobTool.setRequired(Collections.singletonList("job"));
return createTool(runTestJobTool);
}
private static Tool findTestReport() {
ToolParameter findTestReport = new ToolParameter();
findTestReport.setName("findTestReport");
findTestReport.setFuncDesc("查询测试任务的测试报告");
findTestReport.setLocalDesc("任务名称,例如:订单模块测试任务、用户中心测试任务");
findTestReport.setRequired(Collections.singletonList("job"));
return createTool(findTestReport);
}
public static List<Tool> getTools() throws InvocationTargetException, IllegalAccessException {
List<Tool> tools = new ArrayList<>();
Method[] methods = Tools.class.getDeclaredMethods();
for (Method method: methods) {
if (!method.getName().equals("createTool") && method.getReturnType().equals(Tool.class)) {
method.setAccessible(true);
tools.add((Tool) method.invoke(null));
}
}
return tools;
}
}
示例中,通过调用createTool来定义回调函数,并通过getTools()将所有定义的回调函数返回。
实现自定义函数逻辑
public class Functions {
public static String runTestJob(String string){
return "执行任务:" + string;
}
public static String findTestReport(String string) {
return "查找测试报告:" + string;
}
public static Map<String, Object> methodMap(String functionName, String params) {
Map<String, Object> map = new HashMap<>();
map.put("runTestJob", runTestJob(params));
map.put("findTestReport", findTestReport(params));
return map;
}
}
由于需要使用平台数据,目前平台功能还没完全实现,所以具体实现逻辑省略。
发送消息给AI
由于我项目用的JDK8+SpringBoot2.x,SpringAI支持JDK17+和SpringBoot3.2以上,所以只能用okhttp自己封装请求接口了。
@Component
public class DeepSeek {
@Value("${deepseek.api-key}")
private String apiKey;
@Value("${deepseek.base-url}")
private String url;
public Map<String, Object> sendMessage(String message) throws InvocationTargetException, IllegalAccessException, IOException {
Map<String, Object> msg = new HashMap<>();
msg.put("role", "user");
msg.put("content", message);
Map<String, Object> bodys = new HashMap<>();
bodys.put("messages", Collections.singletonList(msg));
bodys.put("model", "deepseek-chat");
bodys.put("tools", Tools.getTools());
Request.Builder build = new Request.Builder().url(url);
Request request;
OkHttpClient okHttpClient = new OkHttpClient().newBuilder().connectTimeout(60, TimeUnit.SECONDS).build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, JSONObject.toJSONString(bodys));
build.addHeader("Content-Type", "application/json");
build.addHeader("Authorization", "Bearer " + apiKey);
request = build.method("POST", body).build();
Map<String, Object> result_message = new HashMap<>();
try {
String response = Objects.requireNonNull(okHttpClient.newCall(request).execute().body()).string();
JSONObject res = JSONObject.parseObject(response);
JSONArray resMsg = res.getJSONArray("choices");
JSONObject result_msg = resMsg.getJSONObject(0).getJSONObject("message");
if (result_msg.containsKey("tool_calls")) {
JSONObject tool = result_msg.getJSONArray("tool_calls").getJSONObject(0);
String functionName = tool.getJSONObject("function").getString("name");
String arguments = JSONObject.parseObject(tool.getJSONObject("function").getString("arguments")).getString("location");
String result = (String) Functions.methodMap(functionName, arguments).get(functionName);
result_message.put("message", result);
result_message.put("type", 0);
return result_message;
}else {
String content = result_msg.getString("content");
result_message.put("message", content);
result_message.put("type", 1);
return result_message;
}
}catch (Exception e) {
result_message.put("message", e.getMessage());
result_message.put("type", 2);
return result_message;
}
}
}
结果展示
运行后,使用PostMan调用,查看返回结果。