模块1(下):用Spring AI从0到1搭一个代码审查助手

0 阅读1分钟

模块1(下):用Spring AI从0到1搭一个代码审查助手

上篇讲了4个核心技巧——角色约束、格式控制、Few-shot、思维链。今天用完整实战把它们串起来,再聊聊温度参数怎么选、以及我踩过的4个坑。

温度参数怎么选

这是另一个让我踩坑的点。

一开始我以为temperature=0最稳定,结果发现模型回答越来越"保守",稍微有点开放性的问题就直接说"这个问题我无法回答"。

后来才明白:temperature控制的是"随机性",不是"准确性"。

场景

Temperature

效果

代码审查、JSON输出

0.1 - 0.3

稳定、可复现

问答、总结、翻译

0.3 - 0.5

平衡稳定和自然

创意写作、头脑风暴

0.7 - 0.9

有创意、不重复

精确计算

0.0

最小随机性

Java代码里的设置:

java

// 精确场景:代码审查
var strictRequest = OpenAiChatOptions.builder()
        .withTemperature(0.1)
        .build();

// 平衡场景:日常问答
var balancedRequest = OpenAiChatOptions.builder()
        .withTemperature(0.5)
        .build();

// 创意场景:起名字、想创意
var creativeRequest = OpenAiChatOptions.builder()
        .withTemperature(0.8)
        .build();

所以:温度不是越高越好,也不是越低越稳定。选对场景就行。

完整实战:Spring AI代码审查助手

来一个能直接跑的完整例子。

Maven依赖

xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

application.yml配置

yaml

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      model: gpt-4o

完整Controller

java

@RestController
@RequestMapping("/api/review")
public class CodeReviewController {

    private final ChatModel chatModel;

    public CodeReviewController(ChatModel chatModel) {
        this.chatModel = chatModel;
    }

    /**
     * 代码审查主接口
     * 结合了:角色约束 + Few-shot + CoT + 结构化输出
     */
    @PostMapping("/java")
    public Map<String, Object> reviewJavaCode(@RequestBody Map<String, String> request) {
        String code = request.get("code");

        String prompt = buildReviewPrompt(code);

        var options = OpenAiChatOptions.builder()
                .withModel("gpt-4o")
                .withTemperature(0.2)  // 低温保证格式稳定
                .withResponseFormat(new ResponseFormat("json_object"))
                .withMaxTokens(2000)
                .build();

        try {
            String result = chatModel.call(new Prompt(prompt, options))
                    .getResult().getOutput().getText();

            return Map.of(
                "success", true,
                "result", parseJson(result),
                "raw", result
            );
        } catch (Exception e) {
            return Map.of(
                "success", false,
                "error", e.getMessage()
            );
        }
    }

    private String buildReviewPrompt(String code) {
        return """
            你是一个拥有15年经验的Java架构师,精通Spring全家桶、并发编程、JVM调优。
            
            请用思维链方式审查代码,先分析逻辑,再指出问题,最后给出评分和建议。
            
            评分标准:
            - 90-100:生产级代码,可直接上线
            - 70-89:有优化空间,需要review
            - 50-69:存在明显风险,需要重构
            - 50以下:建议重写
            
            审查代码:
            %s
            
            请返回JSON格式(不要添加任何解释性文字):
            {
                "score": 分数,
                "grade": "等级描述",
                "thinking": ["思考步骤1", "思考步骤2", "思考步骤3"],
                "issues": [
                    {
                        "severity": "high|medium|low",
                        "type": "NPE|并发|性能|安全|规范",
                        "location": "代码位置",
                        "description": "问题描述",
                        "suggestion": "修改建议"
                    }
                ],
                "summary": "整体评价"
            }
            """.formatted(code);
    }

    private Map<String, Object> parseJson(String json) {
        // 实际项目中建议使用Jackson解析
        // 这里简化处理
        return Map.of("raw", json);
    }
}

测试请求

bash

curl -X POST http://localhost:8080/api/review/java \
  -H "Content-Type: application/json" \
  -d '{
    "code": "public String getUserName(Integer userId) { return userDao.findById(userId).getName(); }"
  }'

返回示例:

json

{
  "success": true,
  "result": {
    "score": 45,
    "grade": "建议重写",
    "thinking": [
      "userId可能是null,直接传入会导致NPE",
      "findById可能返回null,调用getName()会NPE",
      "整体缺乏参数校验和异常处理"
    ],
    "issues": [
      {
        "severity": "high",
        "type": "NPE",
        "location": "第1行",
        "description": "Integer参数可能为null",
        "suggestion": "添加 Objects.requireNonNull(userId) 或判空处理"
      },
      {
        "severity": "high",
        "type": "NPE",
        "location": "第1行",
        "description": "findById可能返回Optional.empty()",
        "suggestion": "使用 Optional.ofNullable() 或 ifPresent()"
      }
    ],
    "summary": "存在严重的空指针风险,建议重构后上线"
  }
}

常见坑,我帮你踩过了

坑1:Prompt越长越好?

不是。Prompt太长会导致:

  • 模型"注意力分散",关键指令被稀释

  • token消耗增加,响应变慢

  • 成本上涨

解法:删除所有"礼貌性废话",只留指令。模型不需要"请您帮我...",直接"帮我..."就行。

坑2:temperature=0就稳定?

不一定。温度控制的是token选择的随机性,但模型本身有"默认行为"。

想让输出稳定,温度+结构化输出要组合使用

java

var options = OpenAiChatOptions.builder()
        .withTemperature(0.1)  // 低温
        .withResponseFormat(new ResponseFormat("json_object"))  // 强制JSON
        .build();

坑3:示例越多越好?

不是。3个示例足够,再多反而会让模型"过度拟合"到示例的风格。

Few-shot的黄金规则:3个示例,覆盖正向、负向、边界情况。

坑4:直接问"你理解了吗?"

没用。模型会说"我理解了",但不代表它真的理解了。

解法:让模型复述任务或给出执行计划,通过输出来验证理解。

今天就能动手的任务

学再多不如动手练。今天给你三个任务,从简单到复杂:

任务1(5分钟):改造你现有的一个Prompt,加上角色约束。

把"帮我写代码"改成:

"你是一个Spring Boot专家,请帮我写一个用户注册接口,包含参数校验和异常处理"

看看回答质量有没有变化。

任务2(15分钟):让ChatGPT返回一个JSON结构化的自我介绍。

用这个Prompt:

"请以JSON格式返回你自己的介绍,包含name、experience(年数)、skills(数组)"

然后在代码里解析这个JSON,感受结构化输出的价值。

任务3(30分钟):用Spring AI写一个带Few-shot的翻译接口。

  • System Prompt定义角色(中英翻译专家)

  • Few-shot给出3个翻译示例

  • 让用户输入中文,返回英文JSON

完整代码可以参考上面的Controller,改改Prompt就行。

总结

这一篇我们用Spring AI把四个技巧——角色约束、格式控制、Few-shot、CoT——串起来,实现了完整的代码审查助手。

关键点回顾:

  • 温度参数要匹配场景,不是越低越好

  • 结构化输出+低温是稳定JSON的黄金组合

  • Few-shot用3个示例覆盖正负向和边界情况

  • 思维链能显著提升复杂任务的准确率

下一篇文章,我们聊模块2:RAG深度调优——怎么让你的AI真正"懂"你的业务数据。

(完整代码已上传到GitHub,文末有链接)

往期文章

[Java转AI,我整理了8个模块的学习路线图](待发布后替换URL)

如果对你有帮助,点个赞再走~