tio-boot 案例 - 增强检索

这篇文章主要介绍,如何使用 tio-boot 框架的其他相关框架完成下面的功能

  • 爬取数据:编写爬虫从网页上爬取数据
  • 数据入库:将爬取的数据存入数据库,这里使用 postgre 数据库
  • 向量化:将入库后的数据的部分字段进行向量化并入库,方便后续搜索
  • 定时任务:使用定时任务每 1 小时更新整体的数据

网页数据分析

这里爬取的数据是是 SJSU 2024 年的 Class Schedule 数据,

  • 网页地址https://www.sjsu.edu/classes/schedules/fall-2024.php
  • 数据字段 Section,Class Number,Mode of Instruction,Course Title,Satisfies,Units,Type,Days,Times,Instructor,Location,Dates,Open Seats,Notes

-发现网页数据数据格式大致如下

<table id="classSchedule" class="responsive display o-table o-table--class-schedule hide" style="width:100%">
  <thead>
    <tr>
      <th class="all" width="10%" data-priority="1">Section</th>
      <th>Class Number</th>
      <th>Mode of Instruction</th>
      <th class="all" data-priority="2">Course Title</th>
      <th>Satisfies</th>
      <th>Units</th>
      <th>Type</th>
      <th data-priority="3">Days</th>
      <th data-priority="4">Times</th>
      <th>Instructor</th>
      <th>Location</th>
      <th data-priority="3">Dates</th>
      <th data-priority="2">Open Seats</th>
      <th>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <a href="https://catalog.sjsu.edu/content.php?filter%5B27%5D=AAS&filter%5B29%5D=1&cur_cat_oid=15&navoid=5382"
          >AAS 1 (Section 04)</a
        >
      </td>
      <td>47610</td>
      <td>In Person</td>
      <td>Introduction to Asian American Studies</td>
      <td>GE: F</td>
      <td>3.0</td>
      <td>LEC</td>
      <td>TR</td>
      <td>12:00PM-01:15PM</td>
      <td><a href="mailto:saugher.nojan@sjsu.edu">Saugher Nojan</a></td>
      <td>HGH122</td>
      <td>08/21/24-12/09/24</td>
      <td>0</td>
      <td>&nbsp;</td>
    </tr>
    <tr>
      <td>
        <a href="https://catalog.sjsu.edu/content.php?filter%5B27%5D=AAS&filter%5B29%5D=1&cur_cat_oid=15&navoid=5382"
          >AAS 1 (Section 05)</a
        >
      </td>
      <td>47611</td>
      <td>In Person</td>
      <td>Introduction to Asian American Studies</td>
      <td>GE: F</td>
      <td>3.0</td>
      <td>LEC</td>
      <td>MW</td>
      <td>03:00PM-04:15PM</td>
      <td><a href="mailto:wayne.jopanda@sjsu.edu">Wayne Jopanda</a></td>
      <td>HGH122</td>
      <td>08/21/24-12/09/24</td>
      <td>0</td>
      <td>&nbsp;</td>
    </tr>
  </tbody>
</table>

创建数据表

通过对网数据的分析,创的数据表如下

CREATE TABLE "public"."rumi_sjsu_class_schedule_2024_fall" (
  "id" "int8" NOT NULL,
  "term" "varchar",
  "section" "varchar",
  "section_url" "varchar",
  "class_number" "varchar",
  "mode_of_instruction" "varchar",
  "course_title" "varchar",
  "satisfies" "varchar",
  "units" "varchar",
  "type" "varchar",
  "days" "varchar",
  "times" "varchar",
  "instructor" "varchar",
  "instructor_email" "varchar",
  "location" "varchar",
  "dates" "varchar",
  "open_seats" "varchar",
  "notes" "text",
  "source_url" "varchar",
  "section_vector" vector(3072),
  "remark" "varchar",
  "creator" "varchar" DEFAULT ''::character varying,
  "create_time" "timestamp" NOT NULL DEFAULT CURRENT_TIMESTAMP,
  "updater" "varchar" DEFAULT ''::character varying,
  "update_time" "timestamp" NOT NULL DEFAULT CURRENT_TIMESTAMP,
  "deleted" "int2" NOT NULL DEFAULT 0,
  "tenant_id" "int8" NOT NULL DEFAULT 0,
)
  • section_vector 用于存储向量,设置维度为 3072,这是目前 openai 的 text-text-embedding-3-large 将文本向量化后的文档

爬取数据并入库

添加依赖库

笔者使用的爬虫框架是 webmagic,数据库操作框架是 api-table,添加依赖如下

<dependency>
  <groupId>com.litongjava</groupId>
  <artifactId>api-table</artifactId>
  <version>${table-to-json.version}</version>
</dependency>

<!-- Apache HTTP Client -->
<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <version>4.5.13</version>
</dependency>

<dependency>
  <groupId>us.codecraft</groupId>
  <artifactId>webmagic-core</artifactId>
  <version>0.9.1</version>
  <exclusions>
    <exclusion>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
    </exclusion>
  </exclusions>
</dependency>

编辑代码

SJSUClassScheduleProcessor.java

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import lombok.extern.slf4j.Slf4j;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;

@Slf4j
public class SJSUClassScheduleProcessor implements PageProcessor {

  private Site site = Site.me().setRetryTimes(3).setSleepTime(5000);

  @Override
  public void process(Page page) {
    Elements rows = page.getHtml().getDocument().select("#classSchedule > tbody > tr");
    String sourceUrl = page.getUrl().toString();

    List<Map<String, Object>> dataList = new ArrayList<>();
    int rowSize = rows.size();
    // log.info("row size:{}", rowSize);
    for (int i = 0; i < rowSize; i++) {
      Element row = rows.get(i);
      Elements columns = row.select("td");
      int size = columns.size(); // 正常请求下值是14

      // log.info("size:{}", size);
      if (size == 14) {
        getDataList(i, row, columns, sourceUrl, dataList);
      } else {
        log.error("columns size is not 14");
      }

    }
    page.putField("dataList", dataList);
    log.info("dataList size:{}", dataList.size());

  }

  /**
   * can be Enrolled columns is 15
   */
  private void getDataList(int i, Element row, Elements columns, String sourceUrl, List<Map<String, Object>> dataList) {
    // get data
    String section = columns.get(0).text().trim();
    String class_number = columns.get(1).text().trim();
    String mode_of_instruction = columns.get(2).text().trim();
    String course_title = columns.get(3).text().trim();
    String satisfies = columns.get(4).text().trim();
    String units = columns.get(5).text().trim();
    String type = columns.get(6).text().trim();
    String days = columns.get(7).text().trim();
    String times = columns.get(8).text().trim();
    String instructor = columns.get(9).text().trim();
    String location = columns.get(10).text().trim();
    String dates = columns.get(11).text().trim();
    String open_seats = columns.get(12).text().trim();
    String notes = columns.get(13).text().trim();

    String section_url = columns.get(0).select("a").attr("href").trim();
    String instructor_email = columns.get(9).select("a").attr("href").trim();

    Map<String, Object> dataMap = new HashMap<>();
    dataMap.put("section", section);
    dataMap.put("class_number", class_number);
    dataMap.put("mode_of_instruction", mode_of_instruction);
    dataMap.put("course_title", course_title);
    dataMap.put("satisfies", satisfies);
    dataMap.put("units", units);
    dataMap.put("type", type);
    dataMap.put("days", days);
    dataMap.put("times", times);
    dataMap.put("instructor", instructor);
    dataMap.put("location", location);
    dataMap.put("dates", dates);
    dataMap.put("open_seats", open_seats);
    dataMap.put("notes", notes);
    dataMap.put("section_url", section_url);
    dataMap.put("instructor_email", instructor_email);
    dataMap.put("source_url", sourceUrl);
    dataMap.put("term", "Fall 2024");
    dataList.add(dataMap);
  }

  @Override
  public Site getSite() {
    return site;
  }

}

SJSUClassSchedulePipeline.java

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.postgresql.util.PGobject;

import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Record;
import com.litongjava.db.utils.PgVectorUtils;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.open.chat.services.VectorService;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;

import lombok.extern.slf4j.Slf4j;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;

@Slf4j
public class SJSUClassSchedulePipeline implements Pipeline {

  @Override
  public void process(ResultItems resultItems, Task task) {

    VectorService vectorService = Aop.get(VectorService.class);
    List<Map<String, Object>> dataList = resultItems.get("dataList");
    List<Record> saveRecords = new ArrayList<>();
    Db.delete("delete from rumi_sjsu_class_schedule_2024_fall");
    for (int i = 0; i < dataList.size(); i++) {
      Map<String, Object> map = dataList.get(i);
      Record record = new Record().setColumns(map);
      record.put("id", SnowflakeIdUtils.id());
      String sectionVector = vectorService.getVector(record.getStr("section"));
      PGobject pgVector = PgVectorUtils.getPgVector(sectionVector);
      record.set("section_vector", pgVector);
      saveRecords.add(record);

      // 批量保存,假设每10条数据进行一次批量保存
      if (saveRecords.size() >= 10 || i == dataList.size() - 1) {
        Db.batchSave("rumi_sjsu_class_schedule_2024_fall", saveRecords, saveRecords.size());
        log.info("Batch saved records up to index: {}", i);
        saveRecords.clear();
      }
    }
  }
}

getVector 方法

  public String getVector(String text) {
    String v = null;
    String sql = "select v from " + TableNames.rumi_embedding + " where t=? and m=?";
    PGobject pGobject = Db.queryFirst(sql, text, OpenAiModels.text_embedding_3_large);
    if (pGobject != null) {
      v = pGobject.getValue();

    } else {
      String model = "text-embedding-3-large";
      float[] embeddingArray = OpenAiClient.embeddingArray(text, model);
      String string = Arrays.toString(embeddingArray);
      long id = SnowflakeIdUtils.id();
      v = (String) string;
      PGobject pgVector = PgVectorUtils.getPgVector(v);
      Record saveRecord = new Record().set("t", text).set("v", pgVector).set("id", id).set("m", model);
      Db.save("rumi_embedding",saveRecord);
    }
    return v;
  }

SJSUClassScheduleService.java

import com.litongjava.open.chat.spider.sjsu.SJSUClassSchedulePipeline;
import com.litongjava.open.chat.spider.sjsu.SJSUClassScheduleProcessor;

import us.codecraft.webmagic.Spider;

public class SJSUClassScheduleService {

  public void index() {
    String url = "https://www.sjsu.edu/classes/schedules/fall-2024.php";
    Spider.create(new SJSUClassScheduleProcessor())
        // url
        .addUrl(url) // Add the url you want to scrape
        .addPipeline(new SJSUClassSchedulePipeline())
        //
        .thread(5).run();
  }
}

上面的代码非常简单,这就不过多解释了

向量化

向量模型

向量模型可以将 文本 转为具有语义信息的向量,这里使用的向量模型是 openai 的 text-text-embedding-3-large,截止了 2024 年 7 月 17 日,这个模型还需要联网调用.笔者使用 java-openai 库调用 openai 的 text-embedding 模型

添加依赖

    <dependency>
      <groupId>com.litongjava</groupId>
      <artifactId>java-openai</artifactId>
      <version>1.0.1</version>
    </dependency>

创建数据表

创建一张数据表,缓存向量结果,放置相同的文本多次向量化

CREATE TABLE "rumi_embedding" (
  "id" "int8" PRIMARY KEY,
  "t" "text" ,
  "m" "varchar" ,
  "v" "vector(3072)",
);

向量化

VectorService.java

  • 调用 OpneAi 的 text-embedding 模型进行向量
  • 将向量结果存入数据库
  • 对外提供向量结果的查询
import java.util.Arrays;

import org.postgresql.util.PGobject;

import com.litongjava.jfinal.plugin.activerecord.Db;
import com.litongjava.jfinal.plugin.activerecord.Record;
import com.litongjava.jfinal.plugin.utils.PgVectorUtils;
import com.litongjava.open.chat.constants.TableNames;
import com.litongjava.openai.client.OpenAiClient;
import com.litongjava.table.utils.SnowflakeIdUtils;

public class VectorService {

  public String getVector(String text) {
    String v = null;
    String sql = "select v from " + TableNames.rumi_embedding + " where t=?";
    PGobject pGobject = Db.queryFirst(sql, text);
    if (pGobject != null) {
      v = pGobject.getValue();

    } else {
      String model = "text-embedding-3-large";
      Float[] embeddingArray = OpenAiClient.embeddingArray(text, model);
      String string = Arrays.toString(embeddingArray);
      long id = SnowflakeIdUtils.id();
      v = (String) string;
      PGobject pgVector = PgVectorUtils.getPgVector(v);
      Record saveRecord = new Record().set("t", text).set("v", pgVector).set("id", id).set("m", model)
          .setTableName("rumi_embedding");
      Db.save(saveRecord);
    }
    return v;
  }

}

VectorController

  • 查询数据表的数据进行向量化
  • 使用异步任务进行
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.jfinal.plugin.activerecord.Db;
import com.litongjava.jfinal.plugin.activerecord.Record;
import com.litongjava.open.chat.constants.TableNames;
import com.litongjava.open.chat.services.VectorService;
import com.litongjava.tio.http.server.annotation.RequestPath;
import com.litongjava.tio.utils.resp.RespVo;
import com.litongjava.tio.utils.string.StrUtils;
import com.litongjava.tio.utils.thread.ThreadUtils;

import lombok.extern.slf4j.Slf4j;

@RequestPath("/vector")
@Slf4j
public class VectorController {
  public RespVo rumi_sjsu_class_schedule_2024_fall() {
    // 使用ExecutorService异步执行任务
    ThreadUtils.getFixedThreadPool().submit(() -> {
      try {
        String selectSql = "select id,section from " + TableNames.rumi_sjsu_class_schedule_2024_fall
            + " where section_vector is null";

        List<Record> records = Db.find(selectSql);

        String updateSql = "update " + TableNames.rumi_sjsu_class_schedule_2024_fall
            + " set section_vector= ? where id=?";

        for (Record record : records) {
          Object id = record.get("id");
          String section = record.getStr("section");

          if (record.get("section_vector") != null) {
            log.info("skip:{},{}", id, section);
            continue;
          }

          if (StrUtils.isEmpty(section)) {
            log.error("please check the data:{},{},{}", TableNames.rumi_sjsu_class_schedule_2024_fall, id, section);
            continue;
          }
          String sectionVector = Aop.get(VectorService.class).getVector(section);

          int update = Db.update(updateSql, sectionVector, id);
          if (update < 1) {
            log.error("update fail:{},{},{}", TableNames.rumi_sjsu_class_schedule_2024_fall, id, sectionVector);
          }

        }
        log.info("finished");
      } catch (Exception e) {
        log.error("expetion", e);
      }
    });

    return RespVo.ok();
  }

}

触发向量任务

访问 endping:/vector/rumi_sjsu_class_schedule_2024_fall 即可触发向量

定时任务

添加库

笔者使用的定时任务框架是 quartz,需要添加的依赖如下

    <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.quartz-scheduler</groupId>
      <artifactId>quartz-jobs</artifactId>
      <version>2.3.0</version>
    </dependency>

定时任务

src\main\resources\config\tio-quartz.properties 内容如下

com.litongjava.open.chat.task.TaskForPerHour = 0 0 */1 * * ?

TaskForPerHour.java

import org.quartz.JobExecutionContext;

import com.litongjava.jfinal.aop.Aop;
import com.litongjava.open.chat.services.SJSUClassScheduleService;
import com.litongjava.tio.utils.quartz.AbstractJobWithLog;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TaskForPerHour extends AbstractJobWithLog {
  @Override
  public void run(JobExecutionContext context) throws Exception {
    log.info("run (SJSUClassScheduleService.class).index()");
    Aop.get(SJSUClassScheduleService.class).index();
  }
}

TaskForPerHour 是一个定时任务,一个小时执行一次

向量查询

向量查询的 SQl 如下

--# rumi_sjsu_class_schedule_2024_fall.vector_search
SELECT
  id,term,section,section_url,class_number,mode_of_instruction,course_title,satisfies,units,type,days,times,instructor,instructor_email,
  location,dates,open_seats,notes,
  (1-(section_vector <=> input_vector)) AS class_name_similarity
FROM
  rumi_sjsu_class_schedule_2024_fall,
  LATERAL (
    VALUES (
      ?::VECTOR(3072)
    )
  ) AS input(input_vector)
where
 (1-(section_vector <=> input_vector)) >0.3
ORDER BY
  (1-(section_vector <=> input_vector)) DESC
LIMIT 10;
package com.litongjava.open.chat.services;

import java.util.List;

import org.junit.Test;

import com.litongjava.jfinal.aop.Aop;
import com.litongjava.jfinal.plugin.activerecord.Db;
import com.litongjava.jfinal.plugin.activerecord.Record;
import com.litongjava.jfinal.plugin.template.SqlTemplates;
import com.litongjava.open.chat.config.DbConfig;
import com.litongjava.table.utils.MarkdownTableUtils;
import com.litongjava.tio.utils.environment.EnvUtils;

public class VectorDatabaseSerchServiceTest2 {

  @Test
  public void test() {
    EnvUtils.load();
    new DbConfig().config();
    String question = "Tell me about CS 46B";
    String vectorString = Aop.get(VectorService.class).getVector(question);
    String classScheduleSql = SqlTemplates.get("rumi_sjsu_class_schedule_2024_fall.vector_search");
    List<Record> classes = Db.find(classScheduleSql, vectorString);
    String table = MarkdownTableUtils.to(classes);
    System.out.println(table);
  }
}

这里假设的用户的问题是 "Tell me about CS 46B";将过向量查询后的数据如下,可以看到 section 字段 大多数的数据都是和 CS 46B 具有语义关系的. 下一步的任务就是编写一个提示词,用户为的问题,查询结果交给大模型处理

idtermsectionsection_urlclass_numbermode_of_instructioncourse_titlesatisfiesunitstypedaystimesinstructorinstructor_emaillocationdatesopen_seatsnotesclass_name_similarity
395424600148852861Fall 2024CS 46B (Section 02)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46B&cur_cat_oid=15&navoid=538244758In PersonIntroduction to Data Structures4.0LECMW03:00PM-04:15PMChung-Wen Tsaomailto:chung-wen.tsao@sjsu.eduSH10008/21/24-12/09/241130.6762653975170425
395424600148852860Fall 2024CS 46B (Section 01)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46B&cur_cat_oid=15&navoid=538242278In PersonIntroduction to Data Structures4.0LECMW04:30PM-05:45PMFaranak Abrimailto:faranak.abri@sjsu.eduSCI14208/21/24-12/09/24390.6732165996511937
395424600148852846Fall 2024CS 46A (Section 02)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46A&cur_cat_oid=15&navoid=538248991In PersonIntroduction to Programming4.0LECTR09:00AM-10:15AMStaffWSQ10908/21/24-12/09/24820.6337127744473038
395424600148852845Fall 2024CS 46A (Section 01)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46A&cur_cat_oid=15&navoid=538242277In PersonIntroduction to Programming4.0LECTR12:00PM-01:15PMSayma Akthermailto:sayma.akther@sjsu.eduSH10008/21/24-12/09/24580.6254571229764769
395424600148852868Fall 2024CS 46B (Section 16)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46B&cur_cat_oid=15&navoid=538244759In PersonIntroduction to Data Structures4.0LABF11:00AM-01:50PMChung-Wen Tsaomailto:chung-wen.tsao@sjsu.eduDH45008/21/24-12/09/24220.6088166832924062
395424600148852869Fall 2024CS 46B (Section 17)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46B&cur_cat_oid=15&navoid=538244760In PersonIntroduction to Data Structures4.0LABF02:00PM-04:50PMStaffDH45008/21/24-12/09/24290.6062307700560641
395424600148852863Fall 2024CS 46B (Section 11)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46B&cur_cat_oid=15&navoid=538242301In PersonIntroduction to Data Structures4.0LABF11:00AM-01:50PMFaranak Abrimailto:faranak.abri@sjsu.eduMH22208/21/24-12/09/2400.6008694350978779
395424600148852867Fall 2024CS 46B (Section 15)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46B&cur_cat_oid=15&navoid=538242305In PersonIntroduction to Data Structures4.0LABF02:00PM-04:50PMChung-Wen Tsaomailto:chung-wen.tsao@sjsu.eduMH22308/21/24-12/09/24310.5992897155017519
395424600148852864Fall 2024CS 46B (Section 12)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46B&cur_cat_oid=15&navoid=538242302In PersonIntroduction to Data Structures4.0LABF02:00PM-04:50PMFaranak Abrimailto:faranak.abri@sjsu.eduMH22208/21/24-12/09/24150.5917533198579384
395424600148852865Fall 2024CS 46B (Section 13)https://catalog.sjsu.edu/content.php?filter%5B27%5D=CS&filter%5B29%5D=46B&cur_cat_oid=15&navoid=538242303In PersonIntroduction to Data Structures4.0LABF11:00AM-01:50PMFaranak Abrimailto:faranak.abri@sjsu.eduMH22308/21/24-12/09/24130.5900395405245417

大模型推理

编写代码,进行推理


import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import com.litongjava.jfinal.aop.Aop;
import com.litongjava.jfinal.plugin.activerecord.Db;
import com.litongjava.jfinal.plugin.activerecord.Record;
import com.litongjava.jfinal.plugin.template.SqlTemplates;
import com.litongjava.open.chat.config.DbConfig;
import com.litongjava.openai.chat.ChatMessage;
import com.litongjava.openai.chat.ChatResponseVo;
import com.litongjava.openai.chat.OpenAiChatRequestVo;
import com.litongjava.openai.client.OpenAiClient;
import com.litongjava.openai.constants.OpenAiModels;
import com.litongjava.table.utils.MarkdownTableUtils;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.json.JsonUtils;

public class ClassLLMAskTest {

  @Test
  public void test() {
    String question = "Who is the professor in CS 46B?";

    EnvUtils.load();
    new DbConfig().config();

    String vectorString = Aop.get(VectorService.class).getVector(question);
    String classScheduleSql = SqlTemplates.get("rumi_sjsu_class_schedule_2024_fall.vector_search");
    List<Record> classes = Db.find(classScheduleSql, vectorString);
    String table = MarkdownTableUtils.to(classes);

    List<ChatMessage> messages = new ArrayList<>();
    // 初始提示词
    messages.add(new ChatMessage("system", "你是一个课程助手"));
    // 数据
    messages.add(new ChatMessage("system", table));
    // 用户问题
    messages.add(new ChatMessage("user", question));
    OpenAiChatRequestVo openAiChatRequestVo = new OpenAiChatRequestVo();
    openAiChatRequestVo.setModel(OpenAiModels.gpt_4_turbo_2024_04_09);
    openAiChatRequestVo.setMessages(messages);

    ChatResponseVo chatResponseVo = OpenAiClient.chatCompletions(openAiChatRequestVo);
    System.out.println(JsonUtils.toJson(chatResponseVo));
  }
}

推理结果

{
  "object": "chat.completion",
  "id": "chatcmpl-9ltBXMJtcf0ZZAT2BMrWMIj4PMs09",
  "system_fingerprint": "fp_595e3bc347",
  "model": "gpt-4-turbo-2024-04-09",
  "choices": [
    {
      "message": {
        "content": "For the CS 46B course sections in Fall 2024 at San Jose State University, there are two professors teaching different sections:\n\n1. **Chung-Wen Tsao**\n   - Teaching Section 02 (LEC), Section 16 (LAB), and Section 15 (LAB).\n\n2. **Faranak Abri**\n   - Teaching Section 01 (LEC), Section 11 (LAB), Section 13 (LAB), and Section 12 (LAB).\n\nSome laboratories (e.g., Section 17) have not specified their instructor and are listed as 'Staff'.",
        "role": "assistant",
        "tool_calls": null
      },
      "index": 0,
      "logprobs": null,
      "delta": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "completion_tokens": 118,
    "prompt_tokens": 1634,
    "total_tokens": 1752
  },
  "created": "1721200575"
}

格式化后的推理结果,通过分析可以返现,大模型精准的给出了回复

For the CS 46B course sections in Fall 2024 at San Jose State University, there are two professors teaching different sections:

  1. Chung-Wen Tsao - Teaching Section 02 (LEC), Section 16 (LAB), and Section 15 (LAB).

  2. Faranak Abri - Teaching Section 01 (LEC), Section 11 (LAB), Section 13 (LAB), and Section 12 (LAB).

Some laboratories (e.g., Section 17) have not specified their instructor and are listed as 'Staff'.