Spring AI(1.1.0):消息元数据

3 阅读2分钟

消息元数据

ChatClient 支持向用户和系统消息添加元数据。元数据提供了有关消息的额外上下文和信息,可由 AI 模型或下游处理使用。

向用户消息添加元数据

您可以使用 metadata() 方法向用户消息添加元数据。

// Adding individual metadata key-value pairs
String response = chatClient.prompt()
    .user(u -> u.text("What's the weather like?")
        .metadata("messageId", "msg-123")
        .metadata("userId", "user-456")
        .metadata("priority", "high"))
    .call()
    .content();

// Adding multiple metadata entries at once
Map<String, Object> userMetadata = Map.of(
    "messageId", "msg-123",
    "userId", "user-456",
    "timestamp", System.currentTimeMillis()
);

String response = chatClient.prompt()
    .user(u -> u.text("What's the weather like?")
        .metadata(userMetadata))
    .call()
    .content();

向系统消息添加元数据

同样,您可以向系统消息添加元数据。

// Adding metadata to system messages
String response = chatClient.prompt()
    .system(s -> s.text("You are a helpful assistant.")
        .metadata("version", "1.0")
        .metadata("model", "gpt-4"))
    .user("Tell me a joke")
    .call()
    .content();

默认元数据支持

您还可以在 ChatClient 构建器级别配置默认元数据。

@Configuration
class Config {
    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder
            .defaultSystem(s -> s.text("You are a helpful assistant")
                .metadata("assistantType", "general")
                .metadata("version", "1.0"))
            .defaultUser(u -> u.text("Default user context")
                .metadata("sessionId", "default-session"))
            .build();
    }
}

元数据验证

ChatClient 验证元数据以确保数据完整性。

  • 元数据键不能为 null 或空。
  • 元数据值不能为 null。
  • 传递 Map 时,键和值都不能包含 null 元素。

元数据验证失败,抛出异常:IllegalArgumentException

访问元数据

元数据包含在生成的 UserMessage 和 SystemMessage 对象中,可以通过消息的 getMetadata() 方法访问。这在 Advisor 中处理消息或检查对话历史记录时特别有用。

示例:MessageMetadataAdvisor

package com.yupi.yuaiagent.advisor;

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 org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import reactor.core.publisher.Flux;

import java.util.Collections;
import java.util.Map;
import java.util.Optional;

/**
 * 演示在 Advisor 中读取 ChatClient 为 User/System 消息设置的元数据(Message metadata),
 * 并同步到本次调用的 context,便于链路内其它组件使用。
 */
@Slf4j
public class MessageMetadataAdvisor implements CallAdvisor, StreamAdvisor {

    /** 放入 context 的 key:当前请求用户消息元数据(只读快照) */
    public static final String CTX_USER_MESSAGE_METADATA = "userMessageMetadata";
    /** 放入 context 的 key:当前请求系统消息元数据(只读快照) */
    public static final String CTX_SYSTEM_MESSAGE_METADATA = "systemMessageMetadata";

    @Override
    public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        return chain.nextCall(enrichRequestWithMessageMetadata(request));
    }

    @Override
    public Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
        return chain.nextStream(enrichRequestWithMessageMetadata(request));
    }

    /**
     * 从 Prompt 中的 UserMessage / SystemMessage 读取 getMetadata(),写入 request.context()。
     */
    private ChatClientRequest enrichRequestWithMessageMetadata(ChatClientRequest request) {
        Prompt prompt = request.prompt();

        UserMessage userMessage = prompt.getUserMessage();
        Map<String, Object> userMeta = userMessage == null
                ? Collections.emptyMap()
                : Optional.ofNullable(userMessage.getMetadata()).orElseGet(Collections::emptyMap);

        SystemMessage systemMessage = prompt.getSystemMessage();
        Map<String, Object> systemMeta = systemMessage == null
                ? Collections.emptyMap()
                : Optional.ofNullable(systemMessage.getMetadata()).orElseGet(Collections::emptyMap);

        if (!userMeta.isEmpty()) {
            log.info("[{}] user message metadata: {}", getName(), userMeta);
            request.context().put(CTX_USER_MESSAGE_METADATA, Map.copyOf(userMeta));
            // 示例:根据业务元数据影响下游行为(其它 Advisor 可读 context)
            Object priority = userMeta.get("priority");
            if ("high".equals(priority)) {
                request.context().put("priorityHigh", Boolean.TRUE);
            }
        }

        if (!systemMeta.isEmpty()) {
            log.info("[{}] system message metadata: {}", getName(), systemMeta);
            request.context().put(CTX_SYSTEM_MESSAGE_METADATA, Map.copyOf(systemMeta));
        }

        return request;
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }
}