Bootstrap

Spring-AI讲解

Spring-AI

langchain(python) langchain4j

官网:

https://spring.io/projects/spring-ai#learn

整合chatgpt

前置准备
  1. open-ai-key:
    https://api.xty.app/register?aff=PuZD
    https://xiaoai.plus/
    https://eylink.cn/
    或者淘宝搜: open ai key
  2. 魔法软件, 由于国家相关法律规定,建议大家自行准备。(中转可暂不准备)
  3. jdk17
    4 springboot3
    示例代码:https://gitee.com/xscodeit/ai-openai-examples.git

实现
在这里插入图片描述

创建完后会发现加入了依赖:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <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>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>

  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
  </dependency>

</dependencies>

设置代理, 如果你请求的大模型的api接口不是国内的。需要将程序设置代理:

public static void main(String[] args) {
    // 设置代理
    String proxy = "127.0.0.1";  // 如果代理在你本机就127.0.0.1 ,  如果代理是其
    他服务器相应设置
    int port = 7890;   //设置科学上网代理的端口,
    System.setProperty("proxyType", "4");
    System.setProperty("proxyPort", Integer.toString(port));
    System.setProperty("proxyHost", proxy);
    System.setProperty("proxySet", "true");

    SpringApplication.run(Application.class, args);

}

设置key:

xushu:
  # 官方(自行配置)
  openai:
    key: ${OPENAI_KEY}
    url: ${OPENAI_URL}
  # 中转(自行配置)
  aicore:
    key: ${OPEN_AI_KEY}
    url: ${OPEN_AI_URL}

spring:
  ai:
    openai:
      api-key: ${xushu.aicore.key}
      base-url: ${xushu.aicore.url}

示例代码:

private final ChatClient chatClient;
private final OpenAiChatModel chatClient2;
private final OpenAiImageModel imageClient;
private final OpenAiAudioTranscriptionModel audioClient;
private final  OpenAiAudioApi openAiAudioApi;


@Value("${OPEN_AI_KEY}")
private String openAiKey;


@GetMapping("/ai/simple")
public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "给我讲个笑话") String message) {
    System.out.println(openAiKey);
    var value=chatClient.prompt()
    .user(message).call().content();
    return Map.of("generation",value );
}

@GetMapping(value="/ai/stream",produces="text/sse;charset=UTF-8")
public Flux<String> stream(@RequestParam(value = "message", defaultValue = "给我讲个笑话") String message ) {
    System.out.println(openAiKey);
    return chatClient.prompt()
    .user(message)
    .stream()
    .content();
}


@GetMapping(value="/ai/img",produces="text/html")
public String image(@RequestParam(value = "message", defaultValue = "猫") String message ) {

    ImageResponse response = imageClient.call(
        new ImagePrompt(message,
                        OpenAiImageOptions.builder()
                        .withQuality("hd")
                        .withN(1)
                        .withModel(OpenAiImageApi.ImageModel.DALL_E_2.getValue())
                        // dall-e-2 256
                        .withHeight(256)
                        .withWidth(256).build()));

    String url = response.getResult().getOutput().getUrl();
    System.out.println(url);
    return "<img src='"+url+"'/>";

}


@GetMapping(value="/ai/audit2text")
public String audit2text() {

    var transcriptionOptions = OpenAiAudioTranscriptionOptions.builder()
    .withResponseFormat(OpenAiAudioApi.TranscriptResponseFormat.TEXT)
    .withTemperature(0f)
    .build();

    // flac、mp3、mp4、mpeg、mpga、m4a、ogg、wav 或 webm。
    var audioFile = new ClassPathResource("/hello.mp3");

    AudioTranscriptionPrompt transcriptionRequest = new AudioTranscriptionPrompt(audioFile, transcriptionOptions);
    AudioTranscriptionResponse response = audioClient.call(transcriptionRequest);

    //openAiAudioApi.createTranscription()
    return response.getResult().getOutput();
}


@GetMapping(value="/ai/text2audit")
public String text2audit() {


    ResponseEntity<byte[]> speech = openAiAudioApi.createSpeech(
        OpenAiAudioApi.SpeechRequest.builder().
        withVoice(OpenAiAudioApi.SpeechRequest.Voice.ONYX).
        withInput("你好,我是徐庶").build());
    byte[] body = speech.getBody();


    // 将byte[]存为 mp3文件
    try {
        writeByteArrayToMp3(body, System.getProperty("user.dir"));
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

    return "ok";
}

public static void writeByteArrayToMp3(byte[] audioBytes, String outputFilePath) throws IOException {
    // 创建FileOutputStream实例
    FileOutputStream fos = new FileOutputStream(outputFilePath+"/xushu.mp3");

    // 将字节数组写入文件
    fos.write(audioBytes);

    // 关闭文件输出流
    fos.close();
}


@GetMapping(value="/ai/mutil")
public String mutilModel(String message,String imgUrl) throws IOException {

    byte[] imageData = new ClassPathResource("/test.png").getContentAsByteArray();

    var userMessage = new UserMessage(
          "这个图片你看出什么?", // content
          List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData))); // media

    ChatResponse response = chatClient.call(new Prompt(userMessage,
          OpenAiChatOptions.builder()
                .withModel(OpenAiApi.ChatModel.GPT_4_TURBO_PREVIEW.getValue())
                .build()));

    return  response.getResult().getOutput().getContent();
}
function-call

AI本身是不具备实时消息能力的, 比如问“现在北京的天气是什么”, AI是不知道的, 这个时候我们需要通过接口来帮助AI完成,大致流程:
在这里插入图片描述

function-call实现的代码:
  1. 在发送文本时, 同时设置Funcation ; 关键代码:.withFunction(“getWaitTime”)
ChatResponse response = chatClient.call(new Prompt(
        List.of(userMessage),
        OpenAiChatOptions.builder()
                .withFunction("getWaitTime")
                .build()));
  1. 当代码执行完call时(AI响应之后), 会再调用getWaitTime对应的bean的apply方法。
@Bean
public Function<WaitTimeService.Request, WaitTimeService.Response> getWaitTime() {
    return new WaitTimeService();
}
  1. 并且会把 getWaitTime该bean的Request作为funcation-call的返回参数, 即可在apply方法中获取Request对应的参数
@Override
public Response apply(Request request) {
    String name =  request.name();
    String location =  request.location();
     // todo...
    return new Response(location+"有10个!");
}
// jdk17新特性 密封类
public record Request(String name,String location) {}
public record Response(String weather) {}

随后Response会再次丢给AI组织语言, 进行响应,最终

源码:

看源码之前, 了解下该接口需要哪些参数:https://platform.openai.com/docs/api-reference/chat/create
请求: userMessage=“长沙有多少叫徐庶的”
调用call方法, 执行openai的远程api请求

@Override
public ChatResponse call(Prompt prompt) {

    ChatCompletionRequest request = createRequest(prompt, false);

    return this.retryTemplate.execute(ctx -> {

       ResponseEntity<ChatCompletion> completionEntity = this.callWithFunctionSupport(request);

       var chatCompletion = completionEntity.getBody();
       if (chatCompletion == null) {
          logger.warn("No chat completion returned for prompt: {}", prompt);
          return new ChatResponse(List.of());
       }

       RateLimit rateLimits = OpenAiResponseHeaderExtractor.extractAiResponseHeaders(completionEntity);

       List<Generation> generations = chatCompletion.choices().stream().map(choice -> {
          return new Generation(choice.message().content(), toMap(chatCompletion.id(), choice))
             .withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null));
       }).toList();

       return new ChatResponse(generations,
             OpenAiChatResponseMetadata.from(completionEntity.getBody()).withRateLimit(rateLimits));
    });
  1. 通过createRequest将withFunction参数解析到request.tools
ChatCompletionRequest request = createRequest(prompt, false);

tools属性:包含了
在这里插入图片描述

关于open-ai对tools参数的说明:https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools
在这里插入图片描述

  1. 来到callWithFunctionSupport 真正调用远程api接口
    protected Resp callWithFunctionSupport(Req request) { Resp response = this.doChatCompletion(request); return this.handleFunctionCallOrReturn(request, response); }
    这里2句关键代码:

  2. this.doChatCompletion(request); 方法会正常请求chat completion 接口并且会带上funcation-call参数并携带tools属性 , 并且返回对话中的funcation-call所需参数(即WaitTimeService.Request的参数)
    在这里插入图片描述

  3. handleFunctionCallOrReturn 执行Function-callback方法, 此时会调用WaitTimeService.apply方法
    a. 拿到之前解析的functionCallback即
    b. 将arguments从Json转换为对象调用WaitTimeService.apply
    c. 将返回的数据再次请求大模型

发展

SpringAI社区非常活跃
在后续的版本都会更新国内常用的大模型
https://github.com/spring-projects/spring-ai/issues?q=Moonshot
在这里插入图片描述

阿里也率先为自己得通义大模型封装了基于SpringAI的spring-cloud-starter-alibaba-ai
https://sca.aliyun.com/?spm=0.29160081.0.0.77ff60c5NGK3QD

;