最近学习了spring-ai官方的examples,链接如下:
spring-ai-examples
通过这些示例,能知道怎么用spring-ai这个框架。

先记录一下spring ai中,advisor API的作用:
简单来说,advisor就相当于spring框架中的aop,可以在一次调用LLM之前和之后,支持补充其他逻辑,比如日志、查看执行相关的数据或指标等。
详细可以参考官方解释:
advisors

advisor相关的三个示例

1、recursive-advisor-demo

这个示例中,使用advisor在调用LLM前后打印请求和响应

static class MyLogAdvisor implements BaseAdvisor {

    private ObjectMapper objectMapper = new ObjectMapper();

	// 和aop类似,order越小越先执行
	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
		print("REQUEST", chatClientRequest.prompt().getInstructions());
		return chatClientRequest;
	}

	@Override
	public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
		print("RESPONSE", chatClientResponse.chatResponse().getResults());
		return chatClientResponse;
	}

	private void print(String label, Object object) {
		System.out.println(label + ":" + this.objectMapper.writeValueAsString(object) + "\n");
	}
}

调用LLM

ChatClient chatClient = chatClientBuilder // @formatter:off
	// 自定义的tool
	.defaultTools(new MyTools())
	// 自定义的advisors
	.defaultAdvisors(new MyLogAdvisor())
	.build(); 
				
var answer = chatClient
	.prompt("What is current weather in Paris?")
	.call()
	.content();

System.out.println(answer);

2、evaluation-recursive-advisor-demo

顾名思义,这个示例使用了执行-评价-执行-… 的流程。

任务

向LLM询问今天巴黎的天气怎么样

流程

每一次LLM回复之后,会连带着问题和上一次答复,输入进另一个充当judge的LLM,这个judge会评估答复是否准确,并给出1-4之间的评分。
当然,获取回复的LLM和judge的角色,是根据不同的prompt区分的。在询问judge时,会将判分标准一同发送。

核心逻辑

这段代码在一个自定义的advisor中,这个advisor执行完,就直接返回给客户端了
这里设置了最大重试次数,避免陷入死循环

public final class SelfRefineEvaluationAdvisor implements CallAdvisor, StreamAdvisor {
	@Override
	public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
		for (int attempt = 1; attempt <= maxRepeatAttempts + 1; attempt++) {
	
			// 1、调用LLM获取回复
			response = callAdvisorChain.copy(this).nextCall(request);
		
			// 2、将回复交给judge评分
			EvaluationResponse evaluation = this.evaluate(chatClientRequest, response);
		
			// 3、judge给了高分,就采纳
			if (evaluation.rating() >= this.successRating) {
				logger.info("Evaluation passed on attempt {}, evaluation: {}", attempt, evaluation);
				return response;
			}
		
			if (attempt > maxRepeatAttempts) {
				return response;
			}
		
			// 4、这里是将judge给的反馈,也发给LLM
			request = this.addEvaluationFeedback(chatClientRequest, evaluation);
		}
	}
}

3、tool-argument-augmenter-demo

这个示例,可以让LLM在选择不同的tool时,同时给出模型选择tool的理由innerThought、分数confidence、模型自己总结的关键点memoryNotes。
先定义一下,嵌入进prompt中传给LLM,LLM会根据这个格式响应

public record AgentThinking(
	@ToolParam(description = "Your step-by-step reasoning for why you're calling this tool and what you expect",
			required = true) String innerThought,

	@ToolParam(description = "Confidence level (low, medium, high) in this tool choice",
			required = false) String confidence,

	@ToolParam(description = "Key insights to remember for future interactions",
			required = true) List<String> memoryNotes) {
}

关键处理

AugmentedToolCallbackProvider<AgentThinking> provider = AugmentedToolCallbackProvider
	.<AgentThinking>builder()
	.toolObject(new MyTools())
	// LLM会输出AgentThinking格式的参数
	.argumentType(AgentThinking.class)
	.argumentConsumer(event -> {
		// 获取到LLM输出的AgentThinking格式的参数后,可以做一系列处理,主要是为了观测LLM是如何推理的
		AgentThinking thinking = event.arguments();

		logger.info("LLM Reasoning: {}", thinking.innerThought());
		logger.info("Confidence: {}", thinking.confidence());
		logger.info("Thinking notest: {}", thinking.memoryNotes());
		logger.info("Tool: {}", event.toolDefinition().name());
	})
	// 在真正调用tool之前,是否需要把AgentThinking这种tool内没有定义过的参数移除
	.removeExtraArgumentsAfterProcessing(true) 
	.build();

MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder().maxMessages(100).build();

ChatClient chatClient = chatClientBuilder
	// 此处使用工具增强
	.defaultToolCallbacks(provider)
	.defaultAdvisors(
		ToolCallAdvisor.builder()
			.advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
			.conversationHistoryEnabled(false).build(),
		MessageChatMemoryAdvisor.builder(chatMemory).order(Ordered.HIGHEST_PRECEDENCE + 1000).build(),
		new MyLogAdvisor())
	.build();
	
var answer = chatClient
	.prompt("What is current weather in Paris?")
	// 为不同的用户设置各自的会话id,隔离
	.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "default-conversation"))
	.call()
	.entity(MyResponse.class);

System.out.println(answer);

官方给出的工作流程

┌─────────────────────────────────────────────────────────────────────────┐
│                          Tool Argument Augmenter Flow                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. User asks: "What is current weather in Paris?"                      │
│                         │                                               │
│                         ▼                                               │
│  2. Tool Definition Augmentation                                        │
│     Original: { location: string }                                      │
│     Augmented: { location: string, innerThought: string,                │
│                  confidence: string, memoryNotes: string[] }            │
│                         │                                               │
│                         ▼                                               │
│  3. LLM Response with Reasoning                                         │
│     {                                                                   │
│       "location": "Paris",                                              │
│       "innerThought": "User wants weather info for Paris...",           │
│       "confidence": "high",                                             │
│       "memoryNotes": ["User interested in Paris weather"]               │
│     }                                                                   │
│                         │                                               │
│                         ▼                                               │
│  4. Argument Consumer processes reasoning (logging, memory storage)     │
│                         │                                               │
│                         ▼                                               │
│  5. Original tool receives only: { "location": "Paris" }                │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

agent范式

接下来介绍examples中给出的5个agent范式

4、chain-workflow

先自定义一个工作流,比如先把想要的数据提取出来、再将数值转为百分比、再降序排序、再生成表格

private static final String[] systemPrompts = {

		// Step 1
		"""
				Extract only the numerical values and their associated metrics from the text.
				Format each as'value: metric' on a new line.
				Example format:
				92: customer satisfaction
				45%: revenue growth""",
		// Step 2
		"""
				Convert all numerical values to percentages where possible.
				If not a percentage or points, convert to decimal (e.g., 92 points -> 92%).
				Keep one number per line.
				Example format:
				92%: customer satisfaction
				45%: revenue growth""",
		// Step 3
		"""
				Sort all lines in descending order by numerical value.
				Keep the format 'value: metric' on each line.
				Example:
				92%: customer satisfaction
				87%: employee satisfaction""",
		// Step 4
		"""
				Format the sorted data as a markdown table with columns:
				| Metric | Value |
				|:--|--:|
				| Customer Satisfaction | 92% | """
};

依次执行工作流

for (String prompt : systemPrompts) {

	// 1. 把上一次结果作为下一次的输入,这就是最简单的工作流
	String input = String.format("{%s}\n {%s}", prompt, response);

	// 2. 调用
	response = chatClient.prompt(input).call().content();

	System.out.println(String.format("\nSTEP %s:\n %s", step++, response));
}

return response;

官方给的流程图
在这里插入图片描述

5、evaluator-optimizer

引入评估优化,有点像evaluation-recursive-advisor-demo中的流程。
这次使用递归实现,只要evaluator觉得不能通过,就一直递归。
这次的目标是实现一个栈。

<user input>
	Implement a Stack in Java with:
	1. push(x)
	2. pop()
	3. getMin()
	All operations should be O(1).
	All inner fields should be private and when used should be prefixed with 'this.'.
</user input>

核心处理

private RefinedResponse loop(String task, String context, List<String> memory,
		List<Generation> chainOfThought) {
	// 每次先调用LLM获取回复
	Generation generation = generate(task, context);
	// 这里会保存,然后下一次调用LLM会用到
	memory.add(generation.response());
	chainOfThought.add(generation);

	// 关键角色evaluator
	EvaluationResponse evaluationResponse = evalute(generation.response(), task);

	// evaluator觉得可以通过才行
	if (evaluationResponse.evaluation().equals(EvaluationResponse.Evaluation.PASS)) {
		return new RefinedResponse(generation.response(), chainOfThought);
	}

	// 把之前的记录都给LLM
	StringBuilder newContext = new StringBuilder();
	newContext.append("Previous attempts:");
	for (String m : memory) {
		newContext.append("\n- ").append(m);
	}
	newContext.append("\nFeedback: ").append(evaluationResponse.feedback());

	// 一直递归下去
	return loop(task, newContext.toString(), memory, chainOfThought);
}

6、orchestrator-workers

这个是流程编排。在prompt中,要求LLM拆解目标,分成n个子任务,这n个子任务依次执行。

负责编排的prompt

Analyze this task and break it down into 2-3 distinct approaches:

Task: {task}

Return your response in this JSON format:
{
	"analysis": "Explain your understanding of the task and which variations would be valuable.
	             Focus on how each approach serves different aspects of the task.",
	"tasks": [
		{
			"type": "formal",
			"description": "Write a precise, technical version that emphasizes specifications"
		},
		{
			"type": "conversational",
			"description": "Write an engaging, friendly version that connects with readers"
		}
	]
}

负责执行子任务的prompt

Generate content based on:
Task: {original_task}
Style: {task_type}
Guidelines: {task_description}

实现

// Step 1: 让LLM来编排
OrchestratorResponse orchestratorResponse = this.chatClient.prompt()
		.user(u -> u.text(this.orchestratorPrompt)
				.param("task", taskDescription))
		.call()
		.entity(OrchestratorResponse.class);

System.out.println(String.format("\n=== ORCHESTRATOR OUTPUT ===\nANALYSIS: %s\n\nTASKS: %s\n",
		orchestratorResponse.analysis(), orchestratorResponse.tasks()));

// Step 2: 每个子task依次执行
List<String> workerResponses = orchestratorResponse.tasks().stream().map(task -> this.chatClient.prompt()
		.user(u -> u.text(this.workerPrompt)
				.param("original_task", taskDescription)
				.param("task_type", task.type())
				.param("task_description", task.description()))
		.call()
		.content()).toList();

System.out.println("\n=== WORKER OUTPUT ===\n" + workerResponses);

官方给的流程图
在这里插入图片描述

7、parallelization-workflow

显而易见,并行工作流

prompt

像这种子任务没有互相依赖,可以使用

Analyze how market changes will impact this stakeholder group.
Provide specific impacts and recommended actions.
Format with clear sections and priorities.

分别将上面的prompt用于下面几个角色

List<String> inputs = List.of(
		"""
				Customers:
				- Price sensitive
				- Want better tech
				- Environmental concerns
				""",

		"""
				Employees:
				- Job security worries
				- Need new skills
				- Want clear direction
				""",

		"""
				Investors:
				- Expect growth
				- Want cost control
				- Risk concerns
				""",

		"""
				Suppliers:
				- Capacity constraints
				- Price pressures
				- Tech transitions

核心处理

public List<String> parallel(String prompt, List<String> inputs, int nWorkers) {
	Assert.notNull(prompt, "Prompt cannot be null");
	Assert.notEmpty(inputs, "Inputs list cannot be empty");
	Assert.isTrue(nWorkers > 0, "Number of workers must be greater than 0");

	ExecutorService executor = Executors.newFixedThreadPool(nWorkers);
	try {
		List<CompletableFuture<String>> futures = inputs.stream()
				.map(input -> CompletableFuture.supplyAsync(() -> {
					try {
						return chatClient.prompt(prompt + "\nInput: " + input).call().content();
					} catch (Exception e) {
						throw new RuntimeException("Failed to process input: " + input, e);
					}
				}, executor))
				.collect(Collectors.toList());

		// Wait for all tasks to complete
		CompletableFuture<Void> allFutures = CompletableFuture.allOf(
				futures.toArray(CompletableFuture[]::new));
		allFutures.join();

		return futures.stream()
				.map(CompletableFuture::join)
				.collect(Collectors.toList());

	} finally {
		executor.shutdown();
	}
}

官方给的流程图
在这里插入图片描述

8、routing-workflow

这种模式是给LLM几个选择,让LLM来决定使用哪种。
官方的示例类似一个客服系统,这里有三个问题

List<String> tickets = List.of(
		"""
				Subject: Can't access my account
				Message: Hi, I've been trying to log in for the past hour but keep getting an 'invalid password' error.
				I'm sure I'm using the right password. Can you help me regain access? This is urgent as I need to
				submit a report by end of day.
				- John""",

		"""
				Subject: Unexpected charge on my card
				Message: Hello, I just noticed a charge of .99 on my credit card from your company, but I thought
				I was on the .99 plan. Can you explain this charge and adjust it if it's a mistake?
				Thanks,
				Sarah""",

		"""
				Subject: How to export data?
				Message: I need to export all my project data to Excel. I've looked through the docs but can't
				figure out how to do a bulk export. Is this possible? If so, could you walk me through the steps?
				Best regards,
				Mike""");

LLM需要根据每个问题,匹配到最适合的角色来解决,比如有财务人员、技术人员、账户人员等。匹配到之后,LLM再充当这个角色来解答问题。
这里有四种人员,分别擅长处理不同的专业问题。

Map<String, String> supportRoutes = Map.of(
		"billing",
		"""
				You are a billing support specialist. Follow these guidelines:
				1. Always start with "Billing Support Response:"
				2. First acknowledge the specific billing issue
				3. Explain any charges or discrepancies clearly
				4. List concrete next steps with timeline
				5. End with payment options if relevant

				Keep responses professional but friendly.

				Input: """,

		"technical",
		"""
				You are a technical support engineer. Follow these guidelines:
				1. Always start with "Technical Support Response:"
				2. List exact steps to resolve the issue
				3. Include system requirements if relevant
				4. Provide workarounds for common problems
				5. End with escalation path if needed

				Use clear, numbered steps and technical details.

				Input: """,

		"account",
		"""
				You are an account security specialist. Follow these guidelines:
				1. Always start with "Account Support Response:"
				2. Prioritize account security and verification
				3. Provide clear steps for account recovery/changes
				4. Include security tips and warnings
				5. Set clear expectations for resolution time

				Maintain a serious, security-focused tone.

				Input: """,

		"product",
		"""
				You are a product specialist. Follow these guidelines:
				1. Always start with "Product Support Response:"
				2. Focus on feature education and best practices
				3. Include specific examples of usage
				4. Link to relevant documentation sections
				5. Suggest related features that might help

				Be educational and encouraging in tone.

				Input: """);
int i = 1;
for (String ticket : tickets) {
	System.out.println("\nTicket " + i++);
	System.out.println("------------------------------------------------------------");
	System.out.println(ticket);
	System.out.println("------------------------------------------------------------");
	System.out.println(routerWorkflow.route(ticket, supportRoutes));
}

public String route(String input, Map<String, String> routes) {

    // LLM来确认应该由谁来解决
    String routeKey = determineRoute(input, routes.keySet());
		// LLM充当对应的角色
    String selectedPrompt = routes.get(routeKey);

    if (selectedPrompt == null) {
        throw new IllegalArgumentException("Selected route '" + routeKey + "' not found in routes map");
    }

    // LLM来处理
    return chatClient.prompt(selectedPrompt + "\nInput: " + input).call().content();
}

agent实现

9、ai-openai-reflection-example

这个例子其实和evaluation-recursive-advisor-demo类似,也是执行-评价-执行…的过程。

核心处理

public class ReflectionAgent {

    private final ChatClient generateChatClient;

    private final ChatClient critiqueChatClient;


    public ReflectionAgent(ChatModel chatModel) {
        this.generateChatClient = ChatClient.builder(chatModel)
                .defaultSystem("""
                        You are a Java programmer tasked with generating high quality Java code.
                        Your task is to Generate the best content possible for the user's request. If the user provides critique,
                        respond with a revised version of your previous attempt.
                        """)
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build())
                .build();

        this.critiqueChatClient = ChatClient.builder(chatModel)
                .defaultSystem("""
                        You are tasked with generating critique and recommendations to the user's generated content.
                        If the user content has something wrong or something to be improved, output a list of recommendations
                        and critiques. If the user content is ok and there's nothing to change, output this: <OK>
                        """)
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build())
                .build();
    }

    public String run(String userQuestion, int maxIterations) {

        String generation = generateChatClient.prompt(userQuestion).call().content();
        System.out.println("##generation\n\n" + generation);
        String critique;
        for (int i = 0; i < maxIterations; i++) {

            critique = critiqueChatClient.prompt(generation).call().content();

            System.out.println("##Critique\n\n" + critique);
            if (critique.contains("<OK>")) {
                System.out.println("\n\nStop sequence found\n\n");
                break;
            }
            generation = generateChatClient.prompt(critique).call().content();
        }
        return generation;
    }
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐