# 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,如有侵权,请联系删除