Sprint AI OpenAI 对接Deepseek,示范Skills,Function Calling Tools使用
理解Skills
Skills 底层逻辑 是啥, 是spring ai 通过跟大模型api调用 ,将skill中的逻辑匹配到 合并到 提示词中 最后一次性 发给 大模型吗
跟RAG一样,对接大模型一直是通过Http APi 接口无状态访问的,只不过将可使用的tools工具 合并到提示词中 让大模型判断 需要调用哪个 tool工具,并生成tool参数,客户端通过结构化大模型返回的数据,如果有需要调用tool工具的返回报文 则 调用本地对应方法,然后将结果再次发送给大模型。 Skill也类似,一方面描述 skill适用的场景,这是大号的tool工具说明,将场景,工具,脚本发送给大模型,也是通过function calling 实现。
也是为了 适应大模型一次处理message的信息量有限问题。 将信息分为目录,和详细内容 进行分级多次调用来适应这个 问题。 目录就是 skill的 场景,思考过程,执行脚本。 场景是第一次全部发给大模型,大模型觉得用那个场景匹配,然后调用思考过程,组成新的提示词,调用大模型,最后是 返回执行脚本的参数,本地根据参数进行执行。
代码验证:

代码实现
POM
<!-- Spring AI OpenAI Starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Agent Utils(支持Agent Skills) -->
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-agent-utils</artifactId>
<version>0.10.0</version>
</dependency>
Tools
package com.haiwei.javaai.tool;
import com.haiwei.javaai.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class OrderTools {
@Tool(description = "根据订单ID查询订单详情,返回订单状态、金额、创建时间等信息")
public Object getOrderById(
@ToolParam(description = "订单ID") Long orderId
) {
Map<String, Object> map = new HashMap<>();
map.put("orderId", orderId);
map.put("price", "2000");
map.put("state", "待发货");
log.info("getOrderById ,result:{}", JsonUtil.toJson(map));
return map;
}
@Tool(description = "查询今日新增订单数量")
public int getTodayOrderCount() {
log.info("getTodayOrderCount ,result:{}", 11);
return 11;
}
@Tool(description = "取消指定订单,需要订单ID")
public boolean cancelOrder(
@ToolParam(description = "要取消的订单ID") Long orderId
) {
log.info("cancelOrder ,orderId:{},result:{}", orderId, false);
return false;
}
}
LoggingAdvisor
用来拦截并打印日志
package com.haiwei.javaai.tool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import reactor.core.publisher.Flux;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class LoggingAdvisor implements CallAdvisor, StreamAdvisor {
private final AtomicInteger round = new AtomicInteger(1);
@Override
public String getName() { return "loggingAdvisor"; }
@Override
public int getOrder() { return 0; }
@Override
public ChatClientResponse adviseCall(ChatClientRequest request,
CallAdvisorChain chain) {
log.info("=== 第 " + round.getAndAdd(1) + " 轮 ===");
log.info("USER: " + truncate(request.prompt().getContents(), 3000));
ChatClientResponse response = chain.nextCall(request);
String output = response.chatResponse().getResult().getOutput().getText();
log.info("ASSISTANT: " + truncate(output, 300));
return response;
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest request,
StreamAdvisorChain chain) {
return chain.nextStream(request);
}
private static String truncate(String text, int maxLen) {
if (text == null) return "null";
return text.length() <= maxLen ? text : text.substring(0, maxLen) + "...";
}
}
Skill定义
目录:

SKILL.md文件:
--- name: team-java-standards description: Review code against team Java coding standards. Use for all code review tasks. --- # Team Java Coding Standards Review ## Instructions 审查代码时,检查以下团队规范: ### 强制规范(违反必须指出) 1. 禁止 @Autowired 字段注入,使用构造器注入 2. Controller 方法必须有 @Operation 注解(Springdoc OpenAPI) 3. 不允许在 Controller 中直接处理异常,使用 @RestControllerAdvice ### 建议规范(违反给出建议) 4. Service 方法建议添加 @Transactional(readOnly=true) 5. DTO 和 Entity 之间必须使用 MapStruct 转换 6. 日志使用 Slf4j,禁止 System.out.println ## Output Format 输出格式: - 🔴 强制规范违反:(行号) 说明 + 问题代码 + 修正代码 - 🟡 建议规范违反:(行号) 说明 + 问题代码 + 修正代码 - ✅ 通过项:简要说明 - 不同问题之间使用 ================================== 隔离
SkillController
加载Skills

访问并加载tools

完整代码
package com.haiwei.javaai.controller;
import com.haiwei.javaai.tool.LoggingAdvisor;
import com.haiwei.javaai.tool.OrderTools;
import lombok.extern.slf4j.Slf4j;
import org.springaicommunity.agent.tools.SkillsTool;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class SkillController {
private final ChatClient chatClient;
private final OrderTools orderTools;
public SkillController(ChatClient.Builder chatClientBuilder, ResourceLoader resourceLoader, OrderTools orderTools) {
// 加载Skill文件
Resource resource = resourceLoader.getResource("classpath:skills");
// 将Skill添加为Skill Tool
ToolCallback skillsTool = SkillsTool.builder()
.addSkillsResource(resource)
.build();
// 生成Chat Client 添加默认Skill 和 日志
this.chatClient = chatClientBuilder
.defaultTools(skillsTool)
.defaultAdvisors(new LoggingAdvisor())
.build();
this.orderTools = orderTools;
}
@GetMapping("/char/get1")
public Object get1() {
// String message = "帮我查一下订单123456的状态";
String code = """
@Controller
public class UserController {
@Autowired
private UserRepository userRepo;
@GetMapping("/user/{id}")
public User getUser(@PathVariable String id) {
System.out.println(123);
return userRepo.findById(id).get();
}
}
""";
String message = "请审查以下Java代码:\n```java\n" + code + "\n```";
log.info("Start call LLM,message:{}");
String content = chatClient.prompt()
.user(message)
.tools(orderTools)
.call().content();
log.info(content);
return content;
}
}
示例1:帮我查一下订单123456的状态
日志:
c.h.javaai.controller.SkillController : Start call LLM,message:{}
com.haiwei.javaai.tool.LoggingAdvisor : === 第 1 轮 ===
com.haiwei.javaai.tool.LoggingAdvisor : USER: 帮我查一下订单123456的状态
com.haiwei.javaai.tool.LoggingAdvisor : ASSISTANT:
com.haiwei.javaai.tool.OrderTools : getOrderById ,result:{"orderId":123456,"price":"2000","state":"待发货"}
com.haiwei.javaai.tool.LoggingAdvisor : === 第 2 轮 ===
com.haiwei.javaai.tool.LoggingAdvisor : USER: 帮我查一下订单123456的状态
com.haiwei.javaai.tool.LoggingAdvisor : ASSISTANT: 订单 **123456** 的当前状态如下:| 字段 | 内容 |
|------|------|
| **订单ID** | 123456 |
| **订单金额** | ¥2,000.00 |
| **订单状态** | **待发货** |目前该订单处于**待发货**状态,商家还未发货。请问还需要其他帮助吗?
2026-07-07T16:48:34.305+08:00 INFO 9500 --- [nio-8080-exec-1] c.h.javaai.controller.SkillController : 订单 **123456** 的当前状态如下:| 字段 | 内容 |
|------|------|
| **订单ID** | 123456 |
| **订单金额** | ¥2,000.00 |
| **订单状态** | **待发货** |目前该订单处于**待发货**状态,商家还未发货。请问还需要其他帮助吗?
结果:

示例2:帮忙审查代码
审查过程日志:
2026-07-07T16:24:04.500+08:00 INFO 16196 --- [nio-8080-exec-1] o.a.c.c.C.[.[localhost].[/javaai] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2026-07-07T16:24:04.500+08:00 INFO 16196 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2026-07-07T16:24:04.503+08:00 INFO 16196 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
2026-07-07T16:24:04.774+08:00 INFO 16196 --- [nio-8080-exec-1] c.h.javaai.controller.SkillController : Start call LLM,message:{}
2026-07-07T16:24:04.970+08:00 INFO 16196 --- [nio-8080-exec-1] com.haiwei.javaai.tool.LoggingAdvisor : === 第 1 轮 ===
2026-07-07T16:24:04.970+08:00 INFO 16196 --- [nio-8080-exec-1] com.haiwei.javaai.tool.LoggingAdvisor : USER: 请审查以下Java代码:
```java
@Controller
public class UserController {
@Autowired
private UserRepository userRepo;@GetMapping("/user/{id}")
public User getUser(@PathVariable String id) {
System.out.println(123);
return userRepo.findById(id).get();
}
}```
2026-07-07T16:24:09.141+08:00 INFO 16196 --- [nio-8080-exec-1] com.haiwei.javaai.tool.LoggingAdvisor : ASSISTANT: I'll invoke the team Java standards skill to review this code.
2026-07-07T16:24:09.187+08:00 INFO 16196 --- [nio-8080-exec-1] com.haiwei.javaai.tool.LoggingAdvisor : === 第 2 轮 ===
2026-07-07T16:24:09.187+08:00 INFO 16196 --- [nio-8080-exec-1] com.haiwei.javaai.tool.LoggingAdvisor : USER: 请审查以下Java代码:
```java
@Controller
public class UserController {
@Autowired
private UserRepository userRepo;@GetMapping("/user/{id}")
public User getUser(@PathVariable String id) {
System.out.println(123);
return userRepo.findById(id).get();
}
}```I'll invoke the team Java standards skill to review this code.
2026-07-07T16:24:14.108+08:00 INFO 16196 --- [nio-8080-exec-1] com.haiwei.javaai.tool.LoggingAdvisor : ASSISTANT: 以下是根据团队 Java 编码规范对该代码的审查结果:---
## Java 代码审查报告
### 🔴 强制规范违反
**1. 禁止 `@Autowired` 字段注入,应使用构造器注入**
- **问题代码(第3行):**
```java
@Autowired
private UserRepository userRepo;
```
- **说明:** 字段注入不利于测试和不可变性,团队规范要求使用构造器注入。
- **修正代码:**
```java
private final UserRepository userRepo;public UserController(Use...
2026-07-07T16:24:14.111+08:00 INFO 16196 --- [nio-8080-exec-1] c.h.javaai.controller.SkillController : ** 结果
审查结果:这个是上面 **结果 的MD展示,实际比这个多。可以看出结果是按照Skill中设置的返回的,如下是对比结果:

可观测性
application.properties
logging.level.org.springframework.ai.chat.observability=DEBUG spring.ai.chat.client.observations.log-prompt=true spring.ai.chat.client.observations.log-completion=true logging.level.org.springframework.ai=TRACE
日志记录:

更多推荐


所有评论(0)