diff --git a/hutool-ai/src/test/java/cn/hutool/ai/model/ollama/OllamaServiceTest.java b/hutool-ai/src/test/java/cn/hutool/ai/model/ollama/OllamaServiceTest.java
new file mode 100644
index 000000000..4b8fb2305
--- /dev/null
+++ b/hutool-ai/src/test/java/cn/hutool/ai/model/ollama/OllamaServiceTest.java
@@ -0,0 +1,244 @@
+package cn.hutool.ai.model.ollama;
+
+import cn.hutool.ai.AIServiceFactory;
+import cn.hutool.ai.ModelName;
+import cn.hutool.ai.core.AIConfigBuilder;
+import cn.hutool.ai.core.Message;
+import cn.hutool.core.bean.BeanPath;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSON;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONUtil;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class OllamaServiceTest {
+ // 创建service
+ OllamaService ollamaService = AIServiceFactory.getAIService(
+ new AIConfigBuilder(ModelName.OLLAMA.getValue())
+ // 这里填写Ollama服务的地址
+ .setApiUrl("http://127.0.0.1:11434")
+ // 这里填写使用的模型
+ .setModel("qwen2.5-coder:32b")
+ .build(),
+ OllamaService.class
+ );
+
+ // 假设有一个Java工程师的Agent提示词
+ String javaEngineerPrompt="# 角色 \n" +
+ "你是一位精通Spring Boot 3.0的资深Java全栈工程师,具备以下核心能力: \n" +
+ "- 精通Spring Boot 3.0新特性与最佳实践 \n" +
+ "- 熟练整合Hutool工具包、Redis数据访问、Feign远程调用、FreeMarker模板引擎 \n" +
+ "- 能输出符合工程规范的代码结构和配置文件 \n" +
+ "- 注重代码可读性与注释规范 \n" +
+ "\n" +
+ "# 任务 \n" +
+ "请完成以下编程任务(按优先级排序): \n" +
+ "1. **核心要求** \n" +
+ " - 使用Spring Boot 3.0构建项目 \n" +
+ " - 必须包含以下依赖: \n" +
+ " - `cn.hutool:hutool-all`(最新版) \n" +
+ " - `org.springframework.boot:spring-boot-starter-data-redis` \n" +
+ " - `org.springframework.cloud:spring-cloud-starter-openfeign` \n" +
+ " - `org.springframework.boot:spring-boot-starter-freemarker` \n" +
+ "2. **约束条件** \n" +
+ " - 代码需符合Java 17语法规范 \n" +
+ " - 每个类必须包含Javadoc风格的类注释 \n" +
+ " - 关键方法需添加`@Api`/`@ApiOperation`注解(若涉及接口) \n" +
+ " - Redis操作需使用`RedisTemplate`实现 \n" +
+ "3. **实现流程** \n" +
+ " ``` \n" +
+ " 1. 生成pom.xml依赖配置 \n" +
+ " 2. 创建基础配置类(如RedisConfig) \n" +
+ " 3. 编写Feign客户端接口 \n" +
+ " 4. 实现FreeMarker模板渲染服务 \n" +
+ " 5. 提供完整Controller示例 \n" +
+ " ``` \n" +
+ "\n" +
+ "# 输出要求 \n" +
+ "请以严格Markdown格式输出,每个模块独立代码块: \n" +
+ "```markdown \n" +
+ "## 1. 项目依赖配置(pom.xml片段) \n" +
+ "```xml \n" +
+ "... \n" +
+ "``` \n" +
+ "\n" +
+ "## 2. Redis配置类 \n" +
+ "```java \n" +
+ "@Configuration \n" +
+ "public class RedisConfig { ... } \n" +
+ "``` \n" +
+ "\n" +
+ "## 3. Feign客户端示例 \n" +
+ "```java \n" +
+ "@FeignClient(name = \"...\") \n" +
+ "public interface ... { ... } \n" +
+ "``` \n" +
+ "\n" +
+ "## 4. FreeMarker模板服务 \n" +
+ "```java \n" +
+ "@Service \n" +
+ "public class TemplateService { ... } \n" +
+ "``` \n" +
+ "\n" +
+ "## 5. 控制器示例 \n" +
+ "```java \n" +
+ "@RestController \n" +
+ "@RequestMapping(\"/example\") \n" +
+ "public class ExampleController { ... } \n" +
+ "``` \n" +
+ "``` \n" +
+ "\n" +
+ "# 示例片段(供格式参考) \n" +
+ "```java \n" +
+ "/** \n" +
+ " * 示例Feign客户端 \n" +
+ " * @since 1.0.0 \n" +
+ " */ \n" +
+ "@FeignClient(name = \"demo-service\", url = \"${demo.service.url}\") \n" +
+ "public interface DemoClient { \n" +
+ "\n" +
+ " @GetMapping(\"/data/{id}\") \n" +
+ " @ApiOperation(\"获取示例数据\") \n" +
+ " ResponseEntity getData(@PathVariable(\"id\") Long id); \n" +
+ "} \n" +
+ "``` \n" +
+ "\n" +
+ "请按此规范输出完整代码结构,确保自动化程序可直接解析生成项目文件。";
+
+ /**
+ * 同步方式调用
+ */
+ @Test
+ @Disabled
+ void testSimple() {
+ final String answer = ollamaService.chat("写一个疯狂星期四广告词");
+ System.out.println(answer);
+ assertNotNull(answer);
+ }
+
+ /**
+ * 按流方式输出
+ */
+ @Test
+ @Disabled
+ void testStream() {
+ AtomicBoolean isDone = new AtomicBoolean(false);
+ AtomicReference errorMessage = new AtomicReference<>();
+ ollamaService.chat("写一个疯狂星期四广告词", data -> {
+ // 输出到控制台
+ JSON streamData = JSONUtil.parse(data);
+ if (streamData.getByPath("error") != null) {
+ isDone.set(true);
+ errorMessage.set(streamData.getByPath("error").toString());
+ return;
+ }
+
+ if ("false".equals(streamData.getByPath("done").toString())) {
+ System.out.print(streamData.getByPath("message.content"));
+ } else if ("true".equals(streamData.getByPath("done").toString())) {
+ isDone.set(true);
+ }
+ });
+ // 轮询检查结束标志
+ while (!isDone.get()) {
+ ThreadUtil.sleep(100);
+ }
+ if (errorMessage.get() != null) {
+ throw new RuntimeException(errorMessage.get());
+ }
+ }
+
+ /**
+ * 带历史上下文的同步方式调用
+ */
+ @Test
+ @Disabled
+ void testSimpleWithHistory(){
+ List messageList=new ArrayList<>();
+ messageList.add(new Message("system",javaEngineerPrompt));
+ messageList.add(new Message("user","帮我写一个Java通过Post方式发送JSON给HTTP接口,请求头带有token"));
+ String result = ollamaService.chat(messageList);
+ System.out.println(result);
+ assertNotNull(result);
+ }
+
+ @Test
+ @Disabled
+ void testStreamWithHistory(){
+ List messageList=new ArrayList<>();
+ messageList.add(new Message("system",javaEngineerPrompt));
+ messageList.add(new Message("user","帮我写一个Java通过Post方式发送JSON给HTTP接口,请求头带有token"));
+ AtomicBoolean isDone = new AtomicBoolean(false);
+ AtomicReference errorMessage = new AtomicReference<>();
+ ollamaService.chat(messageList, data -> {
+ // 输出到控制台
+ JSON streamData = JSONUtil.parse(data);
+ if (streamData.getByPath("error") != null) {
+ isDone.set(true);
+ errorMessage.set(streamData.getByPath("error").toString());
+ return;
+ }
+
+ if ("false".equals(streamData.getByPath("done").toString())) {
+ System.out.print(streamData.getByPath("message.content"));
+ } else if ("true".equals(streamData.getByPath("done").toString())) {
+ isDone.set(true);
+ }
+ });
+ // 轮询检查结束标志
+ while (!isDone.get()) {
+ ThreadUtil.sleep(100);
+ }
+ if (errorMessage.get() != null) {
+ throw new RuntimeException(errorMessage.get());
+ }
+ }
+
+ /**
+ * 列出所有已经拉取到服务器上的模型
+ */
+ @Test
+ @Disabled
+ void testListModels(){
+ String models = ollamaService.listModels();
+ JSONArray modelList = JSONUtil.parse(models).getByPath("models", JSONArray.class);
+ for (Object o : modelList) {
+ System.out.println(BeanPath.create("name").get(o));
+ }
+ }
+
+ /**
+ * 让Ollama拉取模型
+ */
+ @Test
+ @Disabled
+ void testPullModel(){
+ String result = ollamaService.pullModel("qwen2.5:0.5b");
+ System.out.println(result);
+ List lines = StrUtil.splitTrim(result, "\n");
+ for (String line : lines) {
+ if(line.contains("error")){
+ throw new RuntimeException(JSONUtil.parse(line).getByPath("error").toString());
+ }
+ }
+ }
+
+ /**
+ * 让Ollama删除已经存在的模型
+ */
+ @Test
+ @Disabled
+ void testDeleteModel(){
+ // 不会返回任何信息
+ System.out.println(ollamaService.deleteModel("qwen2.5:0.5b"));
+ }
+}