SpringAI完整学习指南(四)

目录

[一、Structured Output / Output Converter 转换](#一、Structured Output / Output Converter 转换)

概念

[四种 Converter](#四种 Converter)

[StructuredOutputConverter 接口](#StructuredOutputConverter 接口)

[三大核心 Converter 对比](#三大核心 Converter 对比)

[BeanOutputConverter 详解](#BeanOutputConverter 详解)

[定义 DTO](#定义 DTO)

[基础用法(ChatModel 层)](#基础用法(ChatModel 层))

[高级用法(ChatClient.entity() 简化版)](#高级用法(ChatClient.entity() 简化版))

[MapOutputConverter 详解](#MapOutputConverter 详解)

示例:生成数字映射

[ChatClient 简化方式](#ChatClient 简化方式)

[ListOutputConverter 详解](#ListOutputConverter 详解)

示例:冰淇淋口味列表

[ChatModel 层手动用法(对比理解)](#ChatModel 层手动用法(对比理解))

[Format 指令的生成原理](#Format 指令的生成原理)

[二、ETL 管道](#二、ETL 管道)

概念

完整架构

[DocumentReader 列表](#DocumentReader 列表)

[DocumentTransformer 列表](#DocumentTransformer 列表)

[一行代码串联 ETL](#一行代码串联 ETL)

[完整代码:PDF 到向量库](#完整代码:PDF 到向量库)

切片策略选择

[三、Observability 可观测性](#三、Observability 可观测性)

为什么重要

集成

自动埋点的指标

[Trace 视图(在 Zipkin/Jaeger 中)](#Trace 视图(在 Zipkin/Jaeger 中))

[Prometheus + Grafana](#Prometheus + Grafana)

[集成 Zipkin + Prometheus](#集成 Zipkin + Prometheus)

[四、Retry & Resilience 弹性机制](#四、Retry & Resilience 弹性机制)

为什么重要

[RetryClient 工作流程](#RetryClient 工作流程)

[Spring Retry 集成](#Spring Retry 集成)

配置自动重试

[包装 ChatClient](#包装 ChatClient)

[Resilience4j 熔断(断路器)](#Resilience4j 熔断(断路器))

退避策略选择

[五、Evaluation 模型评估](#五、Evaluation 模型评估)

概念

[内置 Evaluator](#内置 Evaluator)

[核心 API](#核心 API)

评估流程

代码示例


一、Structured Output / Output Converter 转换

概念

.entity() 内部其实用的就是 Output Converter。Spring AI 提供 4 种,直接控制模型输出的格式与反序列化方式。

核心原理(两步走)

  1. 调用前:转换器将期望的输出格式指令(Format Instruction)注入到 Prompt 中,引导模型生成指定结构。

  2. 调用后:转换器将模型返回的文本解析并反序列化为 Java 对象。

四种 Converter

Converter 输出类型
BeanOutputConverter 单个 Java 对象(POJO/Record)
ListOutputConverter List<String>
MapOutputConverter Map<String,Object>
IntegerOutputConverter / BigDecimalOutputConverter 单值

StructuredOutputConverter 接口

java 复制代码
StructuredOutputConverter<T>` 是顶层接口,它同时继承了 Spring 的 `Converter<String, T>` 和自定义的 `FormatProvider
  • FormatProvider.getFormat() --- 返回格式指令字符串,注入 Prompt 告诉模型"你应该按什么格式输出"

  • Converter<String, T> --- 将模型的文本输出转换为目标类型 T 的实例

三大核心 Converter 对比

Converter 目标类型 格式策略 典型场景
BeanOutputConverter<T> Java Bean / Record JSON Schema(DRAFT_2020_12) 业务 DTO、实体类映射,适合字段固定的业务 DTO
MapOutputConverter Map<String, Object> RFC8259 标准 JSON 动态字段探索、快速原型,适合字段不确定的探索场景
ListOutputConverter List<String> 逗号分隔文本 关键词列表、标签提取,适合关键词、标签等简单列表

BeanOutputConverter 详解

BeanOutputConverter 是生产环境最常用 的转换器。它根据 Java 类生成 JSON Schema,引导模型输出符合结构的 JSON,然后用 Jackson ObjectMapper 反序列化为 Java 对象。

定义 DTO

推荐使用 Java Record,配合 Jackson 注解提供字段描述:

java 复制代码
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import java.util.List;

public record ActorsBooks(
    @JsonProperty("actor")
    @JsonPropertyDescription("作者姓名")
    String actor,

    @JsonProperty("books")
    @JsonPropertyDescription("该作者创作的书籍列表")
    List<String> books
) {}

注解说明

@JsonProperty --- 明确指定 JSON 键名,避免因命名风格差异导致映射失败。 @JsonPropertyDescription --- 提供字段语义描述,Spring AI 会将其注入 Prompt,帮助模型理解字段含义。

基础用法(ChatModel 层)

java 复制代码
// 1. 创建转换器
BeanOutputConverter<ActorsBooks> converter =
    new BeanOutputConverter<>(ActorsBooks.class);

// 2. 获取格式指令
String format = converter.getFormat();

// 3. 构造 Prompt(将 format 注入模板)
String template = """
    Generate the filmography of 5 movies for {actor}.
    {format}
    """;

Prompt prompt = new Prompt(
    new PromptTemplate(template,
        Map.of("actor", "Tom",
               "format", format))
    .createMessage()
);

// 4. 调用模型
Generation generation = chatClient.call(prompt).getResult();

// 5. 转换结果
ActorsBooks result = converter.convert(
    generation.getOutput().getContent()
);

高级用法(ChatClient.entity() 简化版)

Spring AI 的 `ChatClient` 提供了更简洁的 `.entity()` API,内部自动完成格式注入和结果转换

java 复制代码
// 单个对象
ActorsBooks result = chatClient.prompt()
    .user("Generate the filmography of 5 books for Tom")
    .call()
    .entity(ActorsBooks.class);

// 列表对象(使用 ParameterizedTypeReference)
List<ActorsBooks> results = chatClient.prompt()
    .user("Generate filmographies for 3 actors")
    .call()
    .entity(new ParameterizedTypeReference<List<ActorsBooks>>() {});

底层原理.entity() 方法内部会:

  1. 调用 converter.getFormat() 获取格式指令、

  2. 将格式指令追加到 system message 中

  3. 调用模型获取文本响应

  4. 调用 converter.convert() 反序列化为 Java 对象

MapOutputConverter 详解

MapOutputConverter 引导模型生成 RFC8259 标准的 JSON,然后转换为 Map<String, Object>。适合字段不确定探索阶段的场景

示例:生成数字映射

java 复制代码
MapOutputConverter converter = new MapOutputConverter();
String format = converter.getFormat();

String template = """
    Provide me a list of {subject}
    {format}
    """;

PromptTemplate promptTemplate = new PromptTemplate(template,
    Map.of(
        "subject", "an array of numbers from 1 to 9 under the key name 'numbers'",
        "format", format
    ));

Prompt prompt = new Prompt(promptTemplate.createMessage());
Generation generation = chatClient.call(prompt).getResult();
Map<String, Object> result = converter.convert(
    generation.getOutput().getContent()
);

// result => {"numbers": [1, 2, 3, 4, 5, 6, 7, 8, 9]}

ChatClient 简化方式

java 复制代码
Map<String, Object> result = chatClient.prompt()
    .user("List 5 popular programming languages with their key features")
    .call()
    .entity(new ParameterizedTypeReference<Map<String, Object>>() {});

ListOutputConverter 详解

ListOutputConverter 引导模型生成逗号分隔 的文本,然后转换为 List<String>。适合提取关键词、标签、简短列表等场景

示例:冰淇淋口味列表

java 复制代码
ListOutputConverter converter =
    new ListOutputConverter(new DefaultConversionService());
String format = converter.getFormat();

String template = """
    List five {subject}
    {format}
    """;

PromptTemplate promptTemplate = new PromptTemplate(template,
    Map.of("subject", "ice cream flavors",
           "format", format));

Prompt prompt = new Prompt(promptTemplate.createMessage());
Generation generation = chatClient.call(prompt).getResult();
List<String> list = converter.convert(
    generation.getOutput().getContent()
);

// list => ["Vanilla", "Chocolate", "Strawberry", "Mint", "Cookie Dough"]

ChatModel 层手动用法(对比理解)

如果不使用 ChatClient 的 .entity() 快捷方法,也可以手动在 ChatModel 层使用 Converter

java 复制代码
@Autowired
private ChatModel chatModel;

public ActorsFilms manualConvert(String input) {
    BeanOutputConverter<ActorsFilms> converter =
        new BeanOutputConverter<>(ActorsFilms.class);

    String format = converter.getFormat();
    SystemMessage systemMessage = new SystemMessage(format);
    UserMessage userMessage = new UserMessage(input);

    String text = chatModel
        .call(new Prompt(systemMessage, userMessage))
        .getResult().getOutput().getText();

    return converter.convert(text);
}

Format 指令的生成原理

`getFormat()` 是 Structured Output 的关键。以 `BeanOutputConverter` 为例,它内部会:

  1. 根据目标 Java 类(如 `ActorsFilms.class`)生成 **JSON Schema**

  2. 将 JSON Schema 包装成一段格式指令文本

  3. 这段文本会告知模型:"你的回复必须是 JSON 格式,不要包含解释,不要包含 Markdown 代码块,必须符合以下 Schema"

二、ETL 管道

概念

ETL(Extract-Transform-Load)是 RAG 的前置环节。Spring AI 把数据摄入抽象为三大组件:

  • DocumentReader :从 PDF/Word/HTML/Markdown/JSON 读取,输出 List<Document>

  • DocumentTransformer :切片、清洗、增强,例如 TokenTextSplitter

  • DocumentWriter:写入 VectorStore / 文件 / 数据库

完整架构

java 复制代码
PDF ──┐
Word ─┼──► DocumentReader ──► DocumentTransformer ──► DocumentWriter ──► VectorStore
HTML ─┘     (Extract)            (Transform)            (Load)			 (向量库)
java 复制代码
// DocumentReader --- 从数据源提取文档
public interface DocumentReader extends Supplier<List<Document>> {
    default List<Document> read() { return get(); }
}

// DocumentTransformer --- 转换文档(拆分、清理、丰富)
public interface DocumentTransformer extends Function<List<Document>, List<Document>> {
    default List<Document> transform(List<Document> docs) { return apply(docs); }
}

// DocumentWriter --- 加载到目标存储
public interface DocumentWriter extends Consumer<List<Document>> {
    default void write(List<Document> docs) { accept(docs); }
}

DocumentReader 列表

Reader artifactId 说明
TextReader spring-ai-core 纯文本
PagePdfDocumentReader spring-ai-pdf-document-reader PDF 按页
ParagraphPdfDocumentReader spring-ai-pdf-document-reader PDF 按段落
TikaDocumentReader spring-ai-tika-document-reader Word/Excel/PPT/HTML/RTF
MarkdownDocumentReader spring-ai-markdown-document-reader Markdown
JsonReader spring-ai-core JSON

DocumentTransformer 列表

Transformer 作用
TokenTextSplitter 按 Token 切片(默认,推荐)
TextSplitter 自定义切片基类
ContentFormatTransformer 添加 markdown/format 标记
KeywordMetadataEnricher 自动提取关键词到 metadata
SummaryMetadataEnricher 用 LLM 自动总结到 metadata

一行代码串联 ETL

java 复制代码
// 函数式写法
vectorStore.accept(tokenTextSplitter.apply(pdfReader.get()));

// 语义化写法
vectorStore.write(tokenTextSplitter.split(pdfReader.read()));

完整代码:PDF 到向量库

java 复制代码
// ========== 1. Maven 依赖 ==========
// pom.xml
// <dependency>
//   <groupId>org.springframework.ai</groupId>
//   <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
// </dependency>
// <dependency>
//   <groupId>org.springframework.ai</groupId>
//   <artifactId>spring-ai-pdf-document-reader</artifactId>
// </dependency>
// <dependency>
//   <groupId>org.springframework.ai</groupId>
//   <artifactId>spring-ai-milvus-store-spring-boot-starter</artifactId>
// </dependency>

// ========== 2. application.yml ==========
// spring:
//   ai:
//     openai:
//       api-key: ${DEEPSEEK_API_KEY}
//       base-url: https://api.deepseek.com
//       embedding:
//         options:
//           model: text-embedding-ada-002
//     vectorstore:
//       milvus:
//         host: localhost
//         port: 19530
//         collection-name: rag_docs
//         embedding-dimension: 1536

// ========== 3. ETL Service ==========
package com.example.demo.etl;

import org.springframework.ai.document.Document;
import org.springframework.ai.reader.DocumentReader;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
import org.springframework.ai.transformer.DocumentTransformer;
import org.springframework.ai.transformer.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;

@Service
public class EtlPipelineService {

    private final VectorStore vectorStore;

    public EtlPipelineService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    // ---------- Demo1: PDF 文档入库 ----------
    public void ingestPdf(String filePath) {
        PagePdfDocumentReader reader = new PagePdfDocumentReader(
            filePath,
            PdfDocumentReaderConfig.builder()
                .withPageTopMargin(0)
                .withPagesPerDocument(1)
                .build()
        );
        TokenTextSplitter splitter = new TokenTextSplitter(
            512,   // defaultChunkSize
            64,    // chunkOverlap
            5,     // minChunkSizeChars
            10000, // maxNumChunks
            true  // keepSeparator
        );
        vectorStore.write(splitter.split(reader.read()));
    }

    // ---------- Demo2: 文本文件入库 ----------
    public void ingestText(Resource file, String category) throws IOException {
        TextReader reader = new TextReader(file);
        reader.getCustomMetadata().put("source", file.getFilename());

        TokenTextSplitter splitter = new TokenTextSplitter();
        List<Document> docs = splitter.apply(reader.get());
        docs.forEach(d -> d.getMetadata().put("category", category));

        vectorStore.add(docs);
    }

    // ---------- Demo3: 相似度检索 ----------
    public List<Document> search(String query, int topK, String category) {
        return vectorStore.similaritySearch(
            SearchRequest.query(query)
                .withTopK(topK)
                .withSimilarityThreshold(0.7)
                .withFilterExpression("category == '" + category + "'")
        );
    }
}

切片策略选择

java 复制代码
原文档 5000 tokens
    │
    ├──► TokenTextSplitter(800, 200)
    │       └─ chunk1: [0-800], chunk2: [600-1400], chunk3: [1200-2000]... (overlap=200)
    │
    └──► ParagraphPdfDocumentReader
            └─ 按段落自然切分,语义完整但长度不均

参数说明:

  • defaultChunkSize: 单 chunk 目标 token(常用 500-1000)

  • minChunkSizeChars: 最小字符数

  • minChunkSizeToEmbed: 可嵌入的最小长度

  • maxNumChunks: 防爆内存上限

三、Observability 可观测性

为什么重要

AI 应用上线后必须能看到:每次调用的 token 用量、哪一步耗时、Advisor 是否生效、RAG 检索质量。Spring AI 原生集成 Micrometer,开箱即用,天然对接 OpenTelemetry 协议,实现 Tracing(分布式追踪)、Metrics(指标监控)、Logging 三位一体。

集成

XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>
java 复制代码
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  tracing:
    sampling:
      probability: 1.0   # 100% 采样(生产建议 0.1)
  metrics:
    tags:
      application: spring-ai-demo

自动埋点的指标

Spring AI 自动采集以下指标(Micrometer Meter):

Meter Name 类型 含义
gen_ai.client.token.usage Counter token 使用量(prompt/completion/total)
gen_ai.client.operation Timer 调用耗时
gen_ai.client.tool.call Counter Tool 调用次数

Trace 视图(在 Zipkin/Jaeger 中)

java 复制代码
trace-id: a1b2c3
├─ span: chat-client "prompt"             (1.2s)
│   ├─ span: advisor "MemoryAdvisor#before"  (5ms)
│   ├─ span: advisor "RAGAdvisor#before"     (50ms)
│   │   └─ span: vector-store similaritySearch (45ms)
│   ├─ span: chat-model "openai.call"        (1.1s)
│   │   └─ tag: gen_ai.usage.prompt_tokens=120
│   │   └─ tag: gen_ai.usage.total_tokens=300
│   └─ span: advisor "MemoryAdvisor#after"   (3ms)

自定义埋点

java 复制代码
@Bean
public ObservationHandler<ChatClientObservationContext> myHandler() {
    return context -> {
        if (context.getError().isPresent()) {
            log.error("AI call failed", context.getError().get());
        }
        ChatResponse resp = context.getResponse();
        if (resp != null) {
            metrics.counter("custom.ai.tokens",
                "model", resp.getMetadata().getModel()
            ).increment(resp.getMetadata().getUsage().getTotalTokens());
        }
    };
}

Prometheus + Grafana

java 复制代码
management:
  endpoints.web.exposure.include: prometheus
  metrics.export.prometheus.enabled: true

Grafana 推荐看板:Spring AI Dashboard(token 趋势、P95 延迟、错误率、Tool 命中率)。

集成 Zipkin + Prometheus

java 复制代码
// ========== 1. Maven 依赖 ==========
// <dependency>
//   <groupId>io.micrometer</groupId>
//   <artifactId>micrometer-tracing-bridge-brave</artifactId>
// </dependency>
// <dependency>
//   <groupId>io.zipkin.reporter2</groupId>
//   <artifactId>zipkin-reporter-brave</artifactId>
// </dependency>
// <dependency>
//   <groupId>org.springframework.boot</groupId>
//   <artifactId>spring-boot-starter-actuator</artifactId>
// </dependency>

// ========== 2. application.yml ==========
// management:
//   endpoints:
//     web:
//       exposure:
//         include: health,metrics,prometheus
//   zipkin:
//     tracing:
//       endpoint: http://localhost:9411/api/v2/spans
//   tracing:
//     sampling:
//       probability: 1.0
//   metrics:
//     export:
//       prometheus:
//         enabled: true

// ========== 3. 启动 Zipkin(Docker) ==========
// docker run -d --name zipkin -p 9411:9411 openzipkin/zipkin:latest

// ========== 4. 查看指标 ==========
// GET /actuator/metrics/spring.ai.chat.client.operation
// GET /actuator/metrics/gen_ai.client.token.usage

// ========== 5. 敏感数据控制 ==========
// 默认:Prompt/Completion 不导出(防泄露)
// spring:
//   ai:
//     chat:
//       observations:
//         include-prompt: false   # true 有泄露风险
//         include-completion: false
//         include-error-logging: true

**安全警告:**include-prompt/completion 设为 true 会将完整对话导出到观测后端,生产环境请谨慎评估!

四、Retry & Resilience 弹性机制

为什么重要

LLM API 经常:

  • 429 限流(尤其免费档)

  • 502/503 网关错误

  • 超时(流式响应慢)

  • 上下文超限

RetryClient 工作流程

Spring Retry 集成

java 复制代码
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

配置自动重试

java 复制代码
@Configuration
@EnableRetry
public class RetryConfig {

    @Bean
    public RetryTemplate chatRetryTemplate() {
        return RetryTemplate.builder()
            .maxAttempts(3)
            .exponentialBackoff(Duration.ofMillis(500), 2, Duration.ofSeconds(10))
            .retryOn(NonTransientAiException.class)
            .retryOn(WebClientResponseException.ServiceUnavailable.class)
            .retryOn(WebClientResponseException.TooManyRequests.class)
            .build();
    }
}

包装 ChatClient

java 复制代码
@Service
public class ResilientChatService {

    private final ChatClient client;
    private final RetryTemplate retry;

    public String chat(String input) {
        return retry.execute(context -> {
            log.info("attempt={}", context.getRetryCount());
            return client.prompt().user(input).call().content();
        });
    }
}

Resilience4j 熔断(断路器)

java 复制代码
@Configuration
public class CircuitConfig {

    @Bean
    public CircuitBreaker aiCircuit() {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .slowCallRateThreshold(80)
            .waitDurationInOpenState(Duration.ofSeconds(30))
            .slidingWindowSize(20)
            .build();
        return CircuitBreaker.of("aiCircuit", config);
    }
}

// 使用
public String chatSafe(String input) {
    return CircuitBreaker.decorateSupplier(aiCircuit,
        () -> client.prompt().user(input).call().content()
    ).get();
}

退避策略选择

java 复制代码
请求失败
    │
    ▼
attempt 1 ──► 429 ──► 等 500ms
    │
    ▼
attempt 2 ──► 429 ──► 等 1000ms (指数退避)
    │
    ▼
attempt 3 ──► 429 ──► 等 2000ms
    │
    ▼
   失败 ──► 触发 CircuitBreaker
    │
    ▼
  fallback(默认答案 / 转人工)

限流原理RateLimiter 基于令牌桶算法 平滑控制请求速率。例如 OpenAI 免费账号限制 3 RPM,可通过此机制主动控制。RetryClient 用装饰器模式包装 AiClient,添加重试能力。

五、Evaluation 模型评估

概念

AI 应用最大痛点:回答对不对?RAG 检索准不准? Spring AI 提供 Evaluator 接口,用 LLM 评估 LLM(或人工对照)。

内置 Evaluator

Evaluator 评估什么
RelevancyEvaluator 答案是否相关于问题
FactCheckingEvaluator 答案是否基于事实(防幻觉)
Evaluator 自定义

核心 API

java 复制代码
// 评估请求
public class EvaluationRequest {
    private final String userText;          // 用户原始输入
    private final List<Content> dataList;   // RAG 检索到的上下文
    private final String responseContent;  // AI 模型响应
}

// 评估响应
public class EvaluationResponse {
    private final boolean passing;   // 是否通过
    private final float score;      // 得分(0.0~1.0)
    private final String feedback;   // 反馈信息
}

// 评估器函数接口
@FunctionalInterface
public interface Evaluator {
    EvaluationResponse evaluate(EvaluationRequest request);
}

评估流程

java 复制代码
测试集: List<{question, expectedAnswer, docs}>
    │
    ▼
对每个 case 调 ChatClient 生成 answer
    │
    ▼
调用 Evaluator 评分: {question, answer, docs} ─► score 0-1
    │
    ▼
聚合统计:平均分、不合格率

代码示例

java 复制代码
@Service
public class RagEvaluationService {

    private final ChatClient evalClient;   // 专门用于评估的 client
    private final ChatClient ragClient;
    private final VectorStore store;

    public void evaluate(List<TestExample> tests) {

        RelevancyEvaluator relevancy = new RelevancyEvaluator(evalClient);
        FactCheckingEvaluator factCheck = new FactCheckingEvaluator(evalClient);

        List<Double> relScores = new ArrayList<>();
        List<Double> factScores = new ArrayList<>();

        for (TestExample t : tests) {
            // 1. 拿到 RAG 答案 + 检索到的 docs
            String answer = ragClient.prompt().user(t.question()).call().content();
            List<Document> docs = store.similaritySearch(
                SearchRequest.builder().query(t.question).topK(4).build());

            // 2. 评估相关性
            EvaluationRequest req = new EvaluationRequest(t.question(), docs, answer);
            EvaluationResponse relResp = relevancy.evaluate(req);
            relScores.add(relResp.score());

            // 3. 评估事实性
            EvaluationResponse factResp = factCheck.evaluate(req);
            factScores.add(factResp.score());
        }

        double avgRel = relScores.stream().mapToDouble(d -> d).average().orElse(0);
        double avgFact = factScores.stream().mapToDouble(d -> d).average().orElse(0);
        log.info("Relevancy: {}, FactCheck: {}", avgRel, avgFact);
    }
}

record TestExample(String question, String expectedAnswer) {}

作者:筱白爱学习!!

欢迎关注转发评论点赞沟通,您的支持是筱白的动力!

相关推荐
VIP_CQCRE4 小时前
用 Ace Data Cloud 快速接入 OpenAI Chat Completions:对话、流式、多轮和多模态一篇打通
python·ai·openai·api·教程
星辰AI7 小时前
ESLint 自定义规则实战:用 AST 分析封堵团队特有的代码坏味道
人工智能·ai·语言模型
图灵传奇8888888 小时前
企业级Multi-Agent协同框架-完整设计文档
ai
AI小码8 小时前
WAIC 2026前瞻:AI产业进入拼落地的下半场
人工智能·算法·ai·程序员·大模型·编程·智能体
小企鹅么么8 小时前
【AI应用开发工程师】第六章:Context Engineering
人工智能·ai
霸道流氓气质8 小时前
Java 工程师 AI 智能体学习路线 · 阶段 3:编程调用 LLM 详解
ai
cyforkk9 小时前
Grok 4.5 与 Grok Build CLI 安装使用指南
ai·grok
ASKCOS9 小时前
深度解析 Kimi Code Swarm 模式:多 Agent 并行编程的架构设计与工程权衡
ai
曦尧11 小时前
Argo CD:面向 Kubernetes 的声明式 GitOps 持续交付工具
ai·自动化