理解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

日志记录:

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐