# xunfei-sdk-java
**Repository Path**: ZhuHJay/xunfei-sdk-java
## Basic Information
- **Project Name**: xunfei-sdk-java
- **Description**: 星火认知大模型Java SDK
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: https://zhuhjay.xyz
- **GVP Project**: No
## Statistics
- **Stars**: 4
- **Forks**: 1
- **Created**: 2023-11-02
- **Last Updated**: 2025-03-30
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Java
## README
### 工程概述
适用于 科大讯飞星火认知大模型 的Java SDK,支持JDK1.8以上使用
> 💡Tip
>
> SDK依赖
>
> | artifactId | version |
> | --------------- | ------- |
> | okhttp | 4.10.0 |
> | hutool-all | 5.8.21 |
> | lombok | 1.18.30 |
> | slf4j-api | 1.7.21 |
> | logback-classic | 1.2.3 |
>
> 💡Tip
>
> 可以用来做什么⁉
>
> 1. 快速接入星火大模型💬,调用 API 更简单
> 2. 可以作为生成鉴权 URL 的生成(内置 XfUtil),保证 appid,APIKey, APISecret 密钥的安全🔐
> - 前端获取鉴权 URL 后使用前端对大模型的调用,还能够自主设置一些响应样式
### 使用介绍
#### 引入SDK
使用Maven进行引入
```xml
com.gitee.zhuhjay
xunfei-sdk-java
0.0.5
```
#### Function Call
下面是我对于 Function Call 的理解
当我有这么一个函数
```java
// 传递时间和位置的信息,从而解析出当天某地的一个天气情况
public void weatherPlugin(String datetime, String location) {}
```
我想让用户通过一句话的方式来触发这个函数,比如:`今天北京的天气如何`,但是这样子很难从一句话中提炼出程序所需要的参数,因为每个人的说话方式都不大相同。这时候 GPT 中存在一个参数 Function,开发者通过这个参数来定义一个方法描述。
```json
{
"name": "weatherPlugin",
"description": "天气插件可以提供天气相关信息。你可以提供指定的地点信息、指定的时间点或者时间段信息,来检索诗词库,精准检索到天气信息。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "地点,比如北京。"
},
"datetime": {
"type": "string",
"description": "日期。"
}
}
}
}
```
例如将上面一个函数通过 Function 的方法描述来进行解析用户输入的这句话,GPT 将会自动提炼出该函数所需要的参数信息,这时候开发者就可以将 GPT 提炼出来的参数传递给函数进行方法的调用。
```json
{
"arguments": "{\"datetime\":\"今天\",\"location\":\"北京\"}",
"name": "weatherPlugin"
}
```
> 💡Tip
>
> 总的来说,就是 Function Call 可以对消息中所涉及 Function 方法描述中的参数进行提炼。
#### 测试案例
- 需要提供 [服务认证信息](https://console.xfyun.cn/services/cbm) 来进行模型的API调用
##### v0.0.5
更新如下
- 新增 V3.5 版本对话(AppClientFactory#openClientV3_5)
- 新增角色 system,用来设置对话背景(Role#SYSTEM)
- 将服务错误码也放进去,方便提示错误信息(ServerCode)
- 整理 pom 文件,依赖版本清晰明了
- 可选是否启用 wss 协议(XfUtil#genAuthUrl)
- 改动 ApiResponse 的状态码以及错误消息
以下更完整的测试案例代码可以参照 v0.0.4
```java
public class Main {
public static void main(String[] args) throws Exception {
List history = new ArrayList<>();
ApiAuth apiAuth = ApiAuth.builder()
.appId("APPID")
.apiKey("API KEY")
.apiSecret("API SECRET").build();
// 创建客户端工厂对象, 可根据实际情况设置并发数
AppClientFactory clientFactory = AppClientFactory.create(apiAuth);
AppClient clientV3 = clientFactory.openClientV3();
Scanner scanner = new Scanner(System.in);
while (true) {
Console.print("me: ");
String content = scanner.nextLine();
if (StrUtil.isBlank(content)) {
continue;
}
if ("exit".equals(content)) {
System.exit(-1);
}
ApiResponse response = clientV3.sendAsync(content, history.toArray(new Text[0])).get();
if (!response.getOk()) {
Console.error("ERROR: code={}, msg={}\n\n", response.getCode(), response.getError());
TimeUnit.MILLISECONDS.sleep(100);
continue;
}
Console.log("ai: {}\n\t\t---tokens {}\n\n", response.getAnswer(), response.getTotalTokens());
// 添加历史记录
history.add(Text.builder().role(Role.USER.getRole()).content(content).build());
history.add(Text.builder().role(Role.ASSISTANT.getRole()).content(response.getAnswer()).build());
}
}
}
```
##### v0.0.4
更新如下
- 添加了 V3 版本以及 Function Call 的使用
- `AppClientFactory` 工厂创建的 `AppClient` 对象将使用懒加载,减少资源浪费
- 合理对 `AppClient` 客户端的线程池进行创建
```java
public class Main {
public static void main(String[] args) throws Exception {
List history = new ArrayList<>();
ApiAuth apiAuth = ApiAuth.builder()
.appId("APPID")
.apiKey("API KEY")
.apiSecret("API SECRET").build();
// 创建客户端工厂对象, 可根据实际情况设置并发数(提前进行设置, 懒加载客户端)
AppClientFactory clientFactory = AppClientFactory.create(apiAuth, 1, 1, 2);
// 构造 Function
List functions = genFunctions();
// 拿到 v3.0 的客户端,这时候才进行该客户端的创建
AppClient clientV3 = clientFactory.openClientV3();
Scanner scanner = new Scanner(System.in);
while (true) {
Console.print("me: ");
String content = scanner.nextLine();
if (StrUtil.isBlank(content)) {
continue;
}
if ("exit".equals(content)) {
break;
}
if ("历史记录".equals(content)) {
history.forEach(text -> Console.log("{}: {}", text.getRole(), text.getContent()));
Console.log("\n\n");
continue;
}
if ("enable fc".equals(content)) {
// 使用 Function Call
clientV3.customFunction(functions);
continue;
}
if ("disable fc".equals(content)) {
// 停用
clientV3.customFunction(null);
continue;
}
ApiResponse response = clientV3.sendAsync(content, history.toArray(new Text[0])).get();
if (!response.getOk()) {
Console.error("ERROR: code={}, msg={}\n\n", response.getCode(), response.getMessage());
TimeUnit.MILLISECONDS.sleep(100);
continue;
}
Console.log("ai: {}\n\t\t---tokens {}\n\n",
response.getAnswer(), response.getTotalTokens());
// 添加历史记录
history.add(Text.builder().role(Role.USER.getRole()).content(content).build());
history.add(Text.builder().role(Role.ASSISTANT.getRole()).content(response.getAnswer()).build());
}
}
}
```
下面是对 Function 的构造,关于参数的详细信息请参考
- [官方API文档](https://www.xfyun.cn/doc/spark/Web.html#_2-function-call%E8%AF%B4%E6%98%8E)
- 源码中 {@link Function}
```java
private static List genFunctions() {
List functions = new ArrayList<>();
// 1. 首先构造 Item
Function.Item item1 = Function.Item.builder()
.type("string")
.description("地点,比如北京。")
.build();
Function.Item item2 = Function.Item.builder()
.type("string")
.description("日期。")
.build();
// 2. 构造 Param
Map properties = new HashMap<>();
properties.put("location", item1);
properties.put("date", item2);
Function.Param param = Function.Param.builder()
.type("object")
.properties(properties)
.required(ListUtil.of("location"))
.build();
// 3. 构造 Function
Function function = Function.builder()
.name("天气查询")
.description("天气插件可以提供天气相关信息。你可以提供指定的地点信息、指定的时间点或者时间段信息,来检索诗词库,精准检索到天气信息。")
.parameters(param)
.build();
functions.add(function);
return functions;
}
```
> 💡Tip
>
> 示例仅供参考,合理性由开发者来进行控制(历史记录)
>
> - **使用 Function Call 尽量不使用历史记录**
##### v0.0.3
总体使用上没有改变
- 对 model 添加序列化接口以及添加空参、满参构造,方便使用
- 添加 `ModelParams` 类可对模型参数进行自定义,都属于可选参数,带有默认值
```java
// 获取一个客户端对象
AppClient client=...;
// 自定义模型
ModelParams model=ModelParams.builder()
.temperature(0.5f) // 采样率:决定结果随机性[0,1]; default=0.5
.maxTokens(2048) // 模型回答的最大长度; v1.5:[1,4096]; v2.0:[1,8192]; default=2048
.topP(4) // 从k个候选中随机选择一个:[1,6]; default=4
.build();
// 给客户端对象设置自定义模型(如果想使用默认的可设置为null)
client.customModelParams(model);
```
bug修复
- 当触发了模型返回的错误信息时造成的线程永久性阻塞(没有释放 synchronized 导致的)
##### v0.0.2
使用 `AppClientFactory` 工厂类来进行 `AppClient` 客户端的创建
- 共用 `OkHttpClient` 连接池,防止过多的创建
- 可设置API并发连接数,底层自动创建线程池,当对象销毁时线程池自动关闭
```java
public class Main {
public static void main(String[] args) throws Exception {
List history = new ArrayList<>();
ApiAuth apiAuth = ApiAuth.builder()
.appId("APPID")
.apiKey("API KEY")
.apiSecret("API SECRET").build();
// 创建客户端工厂对象, 可根据实际情况设置并发数
AppClientFactory clientFactory = AppClientFactory.create(apiAuth, 1);
// 获取客户端对象,目前有两个版本,v1.5和v2.0
AppClient clientV1 = clientFactory.openClientV1();
AppClient clientV2 = clientFactory.openClientV2();
Scanner scanner = new Scanner(System.in);
AtomicInteger counter = new AtomicInteger(0);
while (true) {
Console.print("me: ");
String content = scanner.nextLine();
if ("exit".equals(content)) {
break;
}
if ("历史记录".equals(content)) {
history.forEach(text -> Console.log("{}: {}", text.getRole(), text.getContent()));
Console.log("\n\n");
continue;
}
// 轮询使用不同客户端
boolean is = counter.getAndIncrement() % 2 == 0;
ApiResponse response = is ?
clientV1.sendAsync(content, history.toArray(new Text[0])).get() :
clientV2.sendAsync(content, history.toArray(new Text[0])).get();
Console.log("ai {}: {}\n\t\t---tokens {}\n\n",
is ? "v1.5" : "v2.0",
response.getAnswer(), response.getTotalTokens());
// 添加历史记录
history.add(Text.builder().role(Role.USER.getRole()).content(content).build());
history.add(Text.builder().role(Role.ASSISTANT.getRole()).content(response.getAnswer()).build());
}
}
}
```
> 💡Tip:
>
> 若想查看调用过程的日志,可将日志级别设置为 trace
##### v0.0.1
`AppClient` 为客户端核心类
- 目前可使用模型有 V1.5 与 V2.0,可自行切换
```java
public class Main {
public static void main(String[] args) throws Exception {
List history = new ArrayList<>();
ApiAuth apiAuth = ApiAuth.builder()
.appId("APPID")
.apiKey("API KEY")
.apiSecret("API SECRET").build();
AppClient client = new AppClient(apiAuth, ApiVersion.V1_5);
// AppClient client = new AppClient(apiAuth, ApiVersion.V2_0);
Scanner scanner = new Scanner(System.in);
while (true) {
Console.print("me: ");
String content = scanner.nextLine();
if ("exit".equals(content)) {
break;
}
if ("历史记录".equals(content)) {
history.forEach(text -> Console.log("{}: {}", text.getRole(), text.getContent()));
Console.log("\n\n");
continue;
}
ApiResponse response = client.sendAsync(content, history.toArray(new Text[0])).get();
Console.log("ai: {}\n\t\t---tokens {}\n\n",
response.getAnswer(), response.getTotalTokens());
// 添加历史记录
history.add(Text.builder().role(Role.USER.getRole()).content(content).build());
history.add(Text.builder().role(Role.ASSISTANT.getRole()).content(response.getAnswer()).build());
}
client.close();
}
}
```
### 更新计划
更新着更新着也会发现问题,以下列出自我认为可以更新的点
- [ ] 获取客户端的方式改为 => `factory.getClient(ApiVersion.V2_0)`,使用一个方法以及指定版本来动态获取
- [ ] 提供 Spring AutoConfiguration 方便 SpringBoot 接入
- [ ] 提供一个线程池来调度所有客户端(目前是每个客户端有单独线程池,都是懒加载的)
- [ ] ....
### 声明
第一次创作SDK,如有侵权,请联系删除